Rust为什么需要明确的生命周期?

我正在阅读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的引用的&f.x :在内部作用域完成之后, f&f.x因此变为无效,并且不应该被分配给x

我的问题是,如果使用明确的 'a生命周期”,例如通过推断对更广泛的范围( x = &f.x; )的引用的非法分配,就可以很容易地分析问题。

那么在哪些情况下,实际上需要明确的生命期来防止免费(或其他类)错误?

其他的答案都有一些要点( fjh需要明确的生命周期的具体例子 ),但是却缺less一个关键的东西:为什么当编译器会告诉你错误的时候需要明确的生命周期?

这实际上与“为什么编译器可以推断它们时需要显式types”是同一个问题。 一个假设的例子:

 fn foo() -> _ { "" } 

当然编译器可以看到我正在返回一个&'static str ,那么为什么程序员必须input它?

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

函数是防止更改代码效果的自然界限。 如果我们允许从代码中彻底检查生命周期,那么一个天真无邪的变化可能会影响生命周期,从而可能导致function上的错误。 这不是一个假设的例子。 据我了解,当你依赖顶级函数的types推断时,Haskell有这个问题。 铁锈在萌芽中扼杀了这个特殊的问题。

编译器还有一个效率上的好处 – 只有函数签名需要被parsing才能validationtypes和生命周期。 更重要的是,它对程序员有效益。 如果我们没有明确的生命期,这个函数做了什么:

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

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

通过推断非法指定更广泛的范围

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

实际上是防止错误所需的明确的生命周期?

一点也不。 生命周期需要防止错误,但需要明确的生命周期来保护程序员的聪明才智。

一个例子:

 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不够长。

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

然而,在types定义中,明确的生命周期是不可避免的。 例如,这里有一个不明确的地方:

 struct RefPair(&u32, &u32); 

这些应该是不同的生活还是应该是相同的? 从使用的angular度来看, struct RefPair<'a, 'b>(&'a u32, &'b u32)struct RefPair<'a>(&'a u32, &'a u32) struct RefPair<'a, 'b>(&'a u32, &'b u32)有很大不同。 现在,对于简单的情况,就像你提供的那样,编译器理论上可以像其他地方一样,延长生命期,但是这样的情况是非常有限的,在编译器中不需要额外的复杂性,清晰度的增益应该是至less是有问题的。

本书的案例devise非常简单 – 因为生活的话题被认为是复杂的。

除了Vladimir Matveev的回答之外,编译器不能轻易推断具有多个参数的函数中的生命周期。

另外我自己的可选的箱子有一个OptionBooltypes的as_slice方法,其签名实际上是:

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

编译器绝对没有办法想出来。

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

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

指定Foo实例不应超过它包含的引用(x字段)。

你在锈书中遇到的例子没有说明这一点,因为f和yvariables同时超出范围。

更好的例子是这样的:

 fn main() { let f : Foo; { let y = &5; f = Foo { x: y }; }; println!("{}", fx); } 

现在f真的超过了fx指向的variables

要添加到Shepmaster的答案 :

如果一个函数接收到两个引用作为参数并返回一个引用,那么函数的实现可能有时会返回第一个引用,有时会返回第二个引用。 要预测给定的调用将返回哪个引用是不可能的。 在这种情况下,由于每个参数引用可能引用与不同生命周期绑定的不同variables,因此不可能推断返回的引用的生命周期。 明确的生命周期有助于避免/澄清这种情况。

同样,如果一个结构体包含两个引用(作为两个成员字段),那么该结构体的成员函数有时可能会返回第一个引用,有时会返回第二个引用。 明确的生命期再次阻止了这种含糊之处。

在一些简单的情况下,编译器可以推断生命期,但是这里的规则很复杂

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

一般来说,只有在从参数派生到过程的情况下才可以返回参考。 在这种情况下,指针结果将始终具有与其中一个参数相同的生命周期; 命名寿命表明哪个参数是。