我正在阅读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后使用(或其他类?)错误?
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.
同样地,如果一个结构体包含两个引用(作为两个成员字段),那么该结构体的成员函数可能有时返回第一个引用,有时返回第二个引用。同样,显式的生存期可以防止这种歧义。
在一些简单的情况下,存在生命期省略,编译器可以推断生命期。
其他答案都有突出的要点(fjh的具体例子中需要显式生命期),但忽略了一个关键问题:为什么编译器会告诉你你弄错了,还需要显式生命期?
这实际上和“编译器可以推断显式类型,为什么还需要显式类型”是同一个问题。一个假设的例子:
fn foo() -> _ {
""
}
当然,编译器可以看到我返回一个&'静态str,那么为什么程序员必须键入它呢?
主要原因是,虽然编译器可以看到你的代码做了什么,但它不知道你的意图是什么。
函数是防止代码更改影响的天然边界。如果我们允许从代码中完全检查生存期,那么一个看似无害的更改可能会影响生存期,这可能会在遥远的函数中导致错误。这不是一个假设的例子。据我所知,当对顶级函数依赖类型推断时,Haskell就会遇到这个问题。Rust将这个问题扼杀在萌芽状态。
对于编译器来说,还有一个效率上的好处——只需要解析函数签名来验证类型和生存期。更重要的是,它可以提高程序员的效率。如果我们没有显式的生存期,这个函数会做什么:
fn foo(a: &u8, b: &u8) -> &u8
不检查源代码是不可能知道的,这将违背大量的编码最佳实践。
通过将引用的非法赋值推断到更广的范围
范围本质上是生命周期。更清楚一点的是,生命周期' A是一个通用的生命周期参数,可以在编译时基于调用站点的特定范围进行专门化。
是否真的需要显式生命周期来防止[…]错误吗?
一点也不。生命周期是用来防止错误的,但是显式生命周期是用来保护程序员仅有的一点理智。
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.
同样地,如果一个结构体包含两个引用(作为两个成员字段),那么该结构体的成员函数可能有时返回第一个引用,有时返回第二个引用。同样,显式的生存期可以防止这种歧义。
在一些简单的情况下,存在生命期省略,编译器可以推断生命期。