cor*_*zza 183 static-analysis reference lifetime 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
Run Code Online (Sandbox Code Playgroud)
我很清楚,编译器阻止的错误是在内部作用域完成后分配给的引用的释放x后使用,f因此&f.x变为无效,并且不应该被分配给x.
我的问题是,在不使用显式 'a生命周期的情况下,可以很容易地分析问题,例如通过推断对更宽范围的引用的非法分配(x = &f.x;).
在哪些情况下实际需要明确的生命周期来防止使用后免费(或其他一些类?)错误?
She*_*ter 192
其他答案都有重点(fjh的具体例子,需要明确的生命周期),但缺少一个关键的事情:为什么在编译器告诉你错误的时候需要明确的生命周期?
这实际上是与"编译器可以推断它们时为什么需要显式类型"相同的问题.一个假设的例子:
fn foo() -> _ {
""
}
Run Code Online (Sandbox Code Playgroud)
当然,编译器可以看到我正在返回一个&'static str,那么为什么程序员必须输入它呢?
主要原因是虽然编译器可以看到你的代码做了什么,但它不知道你的意图是什么.
函数是防火墙更改代码效果的自然边界.如果我们要从代码中完全检查生命周期,那么无辜的变化可能会影响生命周期,这可能会导致远程函数出错.这不是一个假设的例子.据我所知,当您依赖顶层函数的类型推断时,Haskell会遇到这个问题.Rust扼杀了这个特殊的问题.
编译器还有一个效率优势 - 只需要解析函数签名以验证类型和生命周期.更重要的是,它为程序员带来了效率优势.如果我们没有明确的生命周期,那么这个函数做了什么:
fn foo(a: &u8, b: &u8) -> &u8
Run Code Online (Sandbox Code Playgroud)
没有检查源代码就不可能分辨出来,这会违反大量的编码最佳实践.
通过推断非法分配对更广范围的引用
范围本质上是生命周期.更清楚的是,生命周期'a是一个通用的生命周期参数,可以在编译时根据调用站点专门针对特定范围.
实际上需要明确的生命周期来防止错误?
一点也不.生存期,需要防止错误,但需要明确的寿命,以保护那点理智程序员们.
fjh*_*fjh 87
我们来看看下面的例子.
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)
};
}
Run Code Online (Sandbox Code Playgroud)
在这里,显性生命周期很重要.这是因为结果与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
Run Code Online (Sandbox Code Playgroud)
小智 12
以下结构中的生命周期注释:
struct Foo<'a> {
x: &'a i32,
}
Run Code Online (Sandbox Code Playgroud)
指定Foo实例不应超过它包含的引用(x字段).
您在书中锈碰到的例子并没有说明这一点,因为f和y变量走出去的范围在同一时间.
一个更好的例子是:
fn main() {
let f : Foo;
{
let n = 5; // variable that is invalid outside this block
let y = &n;
f = Foo { x: y };
};
println!("{}", f.x);
}
Run Code Online (Sandbox Code Playgroud)
现在,f真正超过了指向的变量f.x.
请注意,除了结构定义之外,该段代码中没有明确的生命周期.编译器完全能够推断生命周期main().
但是,在类型定义中,显式生命期是不可避免的.例如,这里有一个含糊不清的地方:
struct RefPair(&u32, &u32);
Run Code Online (Sandbox Code Playgroud)
它们应该是不同的生命周期还是应该是相同的?从使用角度来看,它确实很重要,struct RefPair<'a, 'b>(&'a u32, &'b u32)非常不同struct RefPair<'a>(&'a u32, &'a u32).
现在,对于简单的情况,就像你提供的那样,编译器理论上可以像在其他地方一样消耗生命周期,但这种情况非常有限,并且在编译器中不值得额外的复杂性,并且这种清晰度的增益将在至少有问题.
我在这里找到了另一个很好的解释:http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references。
一般来说,只有从过程的参数派生的引用才可能返回。在这种情况下,指针结果将始终与参数之一具有相同的生命周期;命名生命周期表明是哪个参数。
本书的案例在设计上非常简单。生命周期的主题被认为是复杂的。
编译器无法轻易推断出具有多个参数的函数的生存期。
另外,我自己的可选板条箱的OptionBool类型带有as_slice方法的签名实际上是:
fn as_slice(&self) -> &'static [bool] { ... }
Run Code Online (Sandbox Code Playgroud)
绝对没有办法让编译器知道这一点。
| 归档时间: |
|
| 查看次数: |
17578 次 |
| 最近记录: |