为什么返回字符串切片而不是usize可以防止对字符串进行突变?

时间:2019-05-30 18:20:46

标签: 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"
}

但是,用f1()替换对f2()的调用会导致编译失败。在我看来,f1()f2()都是不可变的借位,而s.clear()是可变的借位,因此在两种情况下我都应该得到编译错误。我想念什么?

3 个答案:

答案 0 :(得分:3)

Rust参考says

  

如果参数中恰好使用了一个生命周期(是否消除了),则将该生命周期分配给所有消除的输出生命周期。

这意味着您的方法

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

解释为:

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

由于"def"的生存期为'static,因此从函数返回时,其生存期可以缩短为'a(因此,编译器在这里不会抱怨),但是在调用函数,编译器无法推断出字符串的真实生存期实际上是'static。为此,您必须自己将其明确标记为'static

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

答案 1 :(得分:3)

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

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);
}

基于功能签名执行生命周期分析。

您将在上面的代码中注意到,我已经使用unimplemented!()作为函数的主体,并且问题完全相同。这是正常

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

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

fn f2<'a>(s: &'a String) -> &'a str;

f2的实现是"def"(生命周期为'static还是&*s(生命周期为'a)无关紧要;仅签名重要,由于省略规则,签名使用相同的生存期。

1 我所知道的一个例外涉及-> impl Trait功能以及结果对象是实现Send还是Sync


对于f1,返回类型未链接到参数,因此参数的借用在对f1的调用结束时结束:

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

f2的情况下,返回类型的寿命与参数相同,因此被认为可以扩展借用。在Rust 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!的顺序来观察差异。

答案 2 :(得分:1)

f1f2都是一成不变的。但是,从f1借用的生存期在f1的结尾处结束,因为您只是返回一个usize而不是实际字符串中的任何内容。

但是,f2返回一个&str,它是借用您的基础Strings。由于n仍然有效,因此s的不变借用一直持续到n不再使用为止。有效地,这可以防止您对s.clear()的调用将指针s的“从下面拉出地毯”。