我正在阅读Rust书的生命周期章节,我看到了一个命名/显式生命周期的例子:

struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let x;                    // -+ x goes into scope
                              //  |
    {                         //  |
        let y = &5;           // ---+ y goes into scope
        let f = Foo { x: y }; // ---+ f goes into scope
        x = &f.x;             //  | | error here
    }                         // ---+ f and y go out of scope
                              //  |
    println!("{}", x);        //  |
}                             // -+ x goes out of scope

我很清楚,编译器阻止的错误是分配给x的引用的use-after-free:内部作用域完成后,f和&f。X变得无效,并且不应该分配给X。

我的问题是,这个问题可以很容易地分析掉,而不使用显式的` a lifetime,例如,通过推断一个更广泛的范围的引用的非法赋值(x = &f.x;)。

在哪些情况下,实际上需要显式生命期来防止free后使用(或其他类?)错误?


当前回答

以下结构中的生命周期注释:

struct Foo<'a> {
    x: &'a i32,
}

指定一个Foo实例不应该比它包含的引用活得更久(x字段)。

你在Rust书中看到的例子并没有说明这一点,因为f和y变量同时超出范围。

一个更好的例子是:

fn main() {
    let f : Foo;
    {
        let n = 5;  // variable that is invalid outside this block
        let y = &n;
        f = Foo { x: y };
    };
    println!("{}", f.x);
}

f确实比f。x指向的变量寿命长。

其他回答

其他答案都有突出的要点(fjh的具体例子中需要显式生命期),但忽略了一个关键问题:为什么编译器会告诉你你弄错了,还需要显式生命期?

这实际上和“编译器可以推断显式类型,为什么还需要显式类型”是同一个问题。一个假设的例子:

fn foo() -> _ {  
    ""
}

当然,编译器可以看到我返回一个&'静态str,那么为什么程序员必须键入它呢?

主要原因是,虽然编译器可以看到你的代码做了什么,但它不知道你的意图是什么。

函数是防止代码更改影响的天然边界。如果我们允许从代码中完全检查生存期,那么一个看似无害的更改可能会影响生存期,这可能会在遥远的函数中导致错误。这不是一个假设的例子。据我所知,当对顶级函数依赖类型推断时,Haskell就会遇到这个问题。Rust将这个问题扼杀在萌芽状态。

对于编译器来说,还有一个效率上的好处——只需要解析函数签名来验证类型和生存期。更重要的是,它可以提高程序员的效率。如果我们没有显式的生存期,这个函数会做什么:

fn foo(a: &u8, b: &u8) -> &u8

不检查源代码是不可能知道的,这将违背大量的编码最佳实践。

通过将引用的非法赋值推断到更广的范围

范围本质上是生命周期。更清楚一点的是,生命周期' A是一个通用的生命周期参数,可以在编译时基于调用站点的特定范围进行专门化。

是否真的需要显式生命周期来防止[…]错误吗?

一点也不。生命周期是用来防止错误的,但是显式生命周期是用来保护程序员仅有的一点理智。

书中的箱子设计非常简单。生命的话题被认为是复杂的。

编译器无法轻易推断具有多个参数的函数的生存期。

另外,我自己的可选板条箱有一个OptionBool类型的as_slice方法,其签名实际上是:

fn as_slice(&self) -> &'static [bool] { ... }

编译器绝对不可能发现这个。

你的例子不能工作的原因很简单,因为Rust只有局部生存期和类型推断。你的建议需要全局推理。当您有一个生命周期不能被省略的引用时,必须对其进行注释。

我在这里找到了另一个很好的解释:http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references。

一般情况下,只有当引用是时才有可能返回 从过程的参数派生。在这种情况下,是指针 Result将始终具有与其中一个参数相同的生命周期; 命名的生命周期指示是哪个参数。

我认为生命周期注释是关于给定ref的契约,它只在接收范围内有效,而在源范围内仍然有效。在同一个生命周期中声明更多的引用会合并作用域,这意味着所有的源引用都必须满足这个契约。 这样的注释允许编译器检查契约的实现。