Rust 为什么返回字符串片段而不是usize会阻止字符串的变异?

Rust 为什么返回字符串片段而不是usize会阻止字符串的变异?,rust,Rust,此代码编译: fn main() { let mut s = String::from("some_string"); let n = f1(&s); s.clear(); println!("n = {}", n); } fn f1(s: &String) -> usize { 10 } fn f2(s: &String) -> &str { "def" } 但是,用f2替换对f1的调用会导致编译

此代码编译:

fn main() {
    let mut s = String::from("some_string");
    let n = f1(&s);
    s.clear();
    println!("n = {}", n);
}

fn f1(s: &String) -> usize {
    10
}

fn f2(s: &String) -> &str {
    "def"
}
但是,用f2替换对f1的调用会导致编译失败。在我看来,f1和f2都执行不可变借用,而s.clear执行可变借用,因此在这两种情况下我都应该得到编译错误。我遗漏了什么?

f1和f2都采用了不可变的借用。但是,从f1借用的生存期在f1结束时结束,因为您只是返回usize,而不是实际字符串中的任何内容

但是,f2返回一个&str,它借用了底层字符串s。由于n仍然存在,s的不可变借用将继续,直到n不再使用为止。这有效地防止了对s.clear的调用从指针s下拉出地毯。

f1和f2都采用了不可变的借用。但是,从f1借用的生存期在f1结束时结束,因为您只是返回usize,而不是实际字符串中的任何内容

但是,f2返回一个&str,它借用了底层字符串s。由于n仍然存在,s的不可变借用将继续,直到n不再使用为止。这有效地防止了您对s.clear的调用从指针s下拉出地毯。

生锈参考:

如果在省略的参数中仅使用了一个生存期,则该生存期将分配给所有省略的输出生存期

这意味着你的方法

fn f2(s: &String) -> &str {
    "def"
}
Rust将其解释为:

fn f2<'a>(s: &'a String) -> &'a str {
    "def"
}
参考文件:

如果在省略的参数中仅使用了一个生存期,则该生存期将分配给所有省略的输出生存期

这意味着你的方法

fn f2(s: &String) -> &str {
    "def"
}
Rust将其解释为:

fn f2<'a>(s: &'a String) -> &'a str {
    "def"
}

以下是重现问题所需的最低代码:

fn f1(s: &String) -> usize { unimplemented!() }

fn f2(s: &String) -> &str { unimplemented!() }

fn main() {
    let mut s = String::from("some_string");
    let n = f1(&s);
    s.clear();
    println!("n = {}", n);
}
生命周期分析基于函数签名执行

您将在上面的代码中注意到,我使用的是未实现的!作为主体的函数,和问题是完全一样的。这是正常的

在绝大多数情况下,1函数签名完全指定了函数的接口,因此无需查看其实现

作为推论,这也意味着返回类型中的生存期是否链接到任何参数中的生存期在签名中完全指定,因此在这种情况下,f2的完整签名是:

fn f2<'a>(s: &'a String) -> &'a str;
在f2的情况下,返回类型与参数具有相同的生存期,因此被视为扩展借用。2015年,借款期限将延长,直到返回值超出借款范围;对于Rust 2018,借用将一直延续到最后一次使用返回值非词汇借用

在您的情况下,两者基本相同:

fn main() {
    let mut s = String::from("some_string");
    let n = {
        //  Immutable borrow of s starts here.
        f2(&s)
    };
    s.clear();  //  Conflicting attempt to mutably borrow s.
    println!("n = {}", n);
    //  Immutable borrow of s ends here.
}

您可以通过切换s.clear和println!的顺序来观察差异

以下是重现问题所需的最低代码:

fn f1(s: &String) -> usize { unimplemented!() }

fn f2(s: &String) -> &str { unimplemented!() }

fn main() {
    let mut s = String::from("some_string");
    let n = f1(&s);
    s.clear();
    println!("n = {}", n);
}
生命周期分析基于函数签名执行

您将在上面的代码中注意到,我使用的是未实现的!作为主体的函数,和问题是完全一样的。这是正常的

在绝大多数情况下,1函数签名完全指定了函数的接口,因此无需查看其实现

作为推论,这也意味着返回类型中的生存期是否链接到任何参数中的生存期在签名中完全指定,因此在这种情况下,f2的完整签名是:

fn f2<'a>(s: &'a String) -> &'a str;
在f2的情况下,返回类型与参数具有相同的生存期,因此被视为扩展借用。2015年,借款期限将延长,直到返回值超出借款范围;对于Rust 2018,借用将一直延续到最后一次使用返回值非词汇借用

在您的情况下,两者基本相同:

fn main() {
    let mut s = String::from("some_string");
    let n = {
        //  Immutable borrow of s starts here.
        f2(&s)
    };
    s.clear();  //  Conflicting attempt to mutably borrow s.
    println!("n = {}", n);
    //  Immutable borrow of s ends here.
}

您可以通过切换s.clear和println!的顺序来观察差异

谢谢,但我还有一个问题。由于f2返回一个与s无关的字符串文字,编译器是否“假定”字符串切片的返回类型表示基础字符串仍然被引用?是。在尝试猜测生命周期时,rustc不会查看函数内部。相反,它只是使用签名。因此,它过于激进地认为f2的静态字符串片段借用了,即使它没有借用。您可以通过显式指定生命周期来生成所需的行为:fn f2’static strtanks,但我还有一个问题。由于f2返回一个与s无关的字符串文字,编译器是否“假定”字符串切片的返回类型表示基础字符串仍然被引用?是。在尝试猜测生命周期时,rustc不会查看函数内部。相反,它只是使用签名。因此,它过于激进地认为f2的静态字符串片段借用了,即使它没有借用。你可以制作d 通过显式指定生命周期来删除行为:fn f2'static str