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