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


当前回答

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

其他回答

你的例子不能工作的原因很简单,因为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.

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

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

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

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

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

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

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

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

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

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