我正在阅读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后使用(或其他类?)错误?


当前回答

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

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

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

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

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

其他回答

让我们来看看下面的例子。

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
    x
}

fn main() {
    let x = 12;
    let z: &u32 = {
        let y = 42;
        foo(&x, &y)
    };
}

在这里,显式生命期很重要。这是因为foo的结果与它的第一个参数('a)具有相同的生命期,所以它可能比它的第二个参数更长寿。这是由foo签名中的生存期名称表示的。如果你在foo调用中切换参数,编译器会抱怨y的生存时间不够长:

error[E0597]: `y` does not live long enough
  --> src/main.rs:10:5
   |
9  |         foo(&y, &x)
   |              - borrow occurs here
10 |     };
   |     ^ `y` dropped here while still borrowed
11 | }
   | - borrowed value needs to live until here

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

If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.

同样地,如果一个结构体包含两个引用(作为两个成员字段),那么该结构体的成员函数可能有时返回第一个引用,有时返回第二个引用。同样,显式的生存期可以防止这种歧义。

在一些简单的情况下,存在生命期省略,编译器可以推断生命期。

注意,除了结构定义之外,这段代码中没有显式的生存期。编译器完全能够在main()中推断生存期。

然而,在类型定义中,显式生存期是不可避免的。例如,这里有一个歧义:

struct RefPair(&u32, &u32);

这是不同的人生还是相同的人生?从使用的角度来看,结构体RefPair<'a, 'b>(&'a u32, &'b u32)与结构体RefPair<'a>(&'a u32, &'a u32)非常不同。

现在,对于简单的情况,就像你提供的那样,理论上编译器可以像在其他地方一样省略生命期,但这样的情况非常有限,不值得在编译器中增加额外的复杂性,这种清晰度的增加至少是有问题的。

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

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