str 类型上 lifetime/borrow 的问题

Problems with lifetime/borrow on str type

为什么这段代码可以编译?

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let x = "eee";
    let &m;
    {
        let y = "tttt";
        m = longest(&x, &y);
    }
    println!("ahahah: {}", m);
}

对我来说,由于生命周期的原因,应该会出现编译错误。 如果我用 i64 编写相同的代码,我会得到一个错误。

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    let x = 3;
    let &m;
    {
        let y = 5;
        m = ooo(&x, &y);
    }
    println!("ahahah: {}", m);
}

错误是:

error[E0597]: `y` does not live long enough
   --> src/main.rs:103:25
    |
103 |       m = ooo(&x, &y);
    |                   ^^ borrowed value does not live long enough
104 |   }
    |   - `y` dropped here while still borrowed
105 |   println!("ahahah: {}", m);
    |                          - borrow later used here

你的例子不完全一样。字符串字面量 "eee" 的类型为 &str,而不是 str,因此对应的整数代码如下所示:

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    let x = &3;
    let &m;
    {
        let y = &5;
        m = ooo(&x, &y);
    }
    println!("ahahah: {}", m);
}

它编译。

这个(和 &str 示例)起作用的原因是因为本地内联文字引用被赋予了 'static 生命周期。也就是说,它们被放置在最终二进制文件的静态数据部分,并且在程序的整个生命周期内都可以在内存中使用。

我们需要了解一些事情才能理解这一点。第一个是字符串文字的类型。任何字符串文字(如 "foo")都具有 &'static str 类型。这是对字符串切片的引用,而且,它是一个 static 引用。这种引用持续整个程序长度,并且可以根据需要强制到任何其他生命周期。

这意味着在您的第一段代码中,x 和 y 已经都是引用并且类型为 &'static str。调用 longest(&x, &y) 仍然有效的原因(即使 &x&y 的类型为 &&'static str)是由于 longest(&x, &y) 确实是 de-加糖为 longest(&*x, &*y) 以使类型匹配。

我们来分析第一段代码中的生命周期。

fn main() {
    // x: &'static str
    let x = "eee";
    // Using let patterns in a forward declaration doesn't really make sense
    // It's used for things like
    // let (x, y) = fn_that_returns_tuple();
    // or
    // let &x = fn_that_returns_reference();
    // Here, it's the same as just `let m;`.
    let &m;
    {
        // y: &'static str
        let y = "tttt";
        // This is the same as `longest(x, y)` due to autoderef
        // m: &'static str
        m = longest(&x, &y);
    }
    // `m: &static str`, so it's still valid here
    println!("ahahah: {}", m);
}

(playground)

对于 let &m;,您的意思可能是 let m: &str 之类的东西来强制其类型。我认为这实际上 确保 m 中引用的生命周期从该前向声明开始。但是由于 m 无论如何都有类型 &'static str,所以没关系。


现在让我们看看第二个版本i64

fn main() {
    // x: i64
    // This is a local variable
    // and will be dropped at the end of `main`.
    let x = 3;
    // Again, this doesn't really make sense.
    let &m;
    // If we change it to `let m: &i64`, the error changes,
    // which I'll discuss below.
    {
        // Call the lifetime roughly corresponding to this block `'block`.
        // y: i64
        // This is a local variable,
        // and will be dropped at the end of the block.
        let y = 5;
        // Since `y` is local, the lifetime of the reference here
        // can't be longer than this block.
        // &y: &'block i64
        // m: &'block i64
        m = ooo(&x, &y);
    } // Now the lifetime `'block` is over.
      // So `m` has a lifetime that's over
      // so we get an error here.
    println!("ahahah: {}", m);
}

(playground)

如果我们将 m 的声明更改为 let m: &i64(我想你的意思就是这个意思),错误就会改变。

error[E0597]: `y` does not live long enough
  --> src/main.rs:26:21
   |
26 |         m = ooo(&x, &y);
   |                     ^^ borrowed value does not live long enough
27 |     } // Now the lifetime `'block` is over.
   |     - `y` dropped here while still borrowed
...
30 |     println!("ahahah: {}", m);
   |                            - borrow later used here

(playground)

所以现在我们明确希望 m 与外部块一样长,但我们不能让 y 持续那么久,所以错误发生在调用 ooo.


由于这两个程序都处理文字,我们实际上可以编译第二个版本。为此,我们必须利用静态提升。可以在 Rust 1.21 announcement (which was the release the introduced this) or at .

中找到对这意味着什么的一个很好的总结。

简而言之,如果我们直接获取对文字值的引用,则该引用可能会提升为静态引用。也就是说,它不再引用局部变量。

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    // due to promotion
    // x: &'static i64
    let x = &3;
    let m;
    {
        // due to promotion
        // y: &'static i64
        let y = &5;
        // m: &'static i64
        m = ooo(x, y);
    }
    // So `m`'s lifetime is still active
    println!("ahahah: {}", m);
}

(playground)