我一直在努力寻找 Rust 生命周期的一个很好的解释。我的想法是,它们是我们告诉编译器哪些变量必须比哪些变量寿命更长的方式,因此它可以验证引用是非悬空的,而不必检查每个代码路径。我不明白的是我们在使用它们时实际上在说什么。举个例子:
fn foo<'a>(bar: &'a i32, baz: &'a i32) -> &'a i32 {
bar
}
fn main() {
let m = 5;
let x = &m;
{
let n = 6;
let y = &n;
{
let z = foo(x, y);
dbg!(z);
}
dbg!(y);
}
dbg!(x);
}
Run Code Online (Sandbox Code Playgroud)
这编译并运行良好。在这里x,y, 和z都活了不同的时间长度。基础数据m和n也存在不同的时间。很明显,当我们声明参数和footo 的返回值都具有生命周期时'a,我们并不是说它们都在程序的完全相同的一段时间内生存。(我尽量不说“一生”,抱歉措辞尴尬。)
我听到的另一种解释是我们说在 、 和返回值的生命周期的交点处存在一些生命周期'a。然而,这将是一个微不足道的声明,因为显然它们都在我们调用时的范围内。barbazfoo
在我看来,我们所说的是,存在某种生命周期,'a使得bar和baz超过'a,并且返回值的寿命超过 'a。这意味着生命周期注释在用于参数和用于返回值时具有不同的含义:这意味着参数上的注释是生命周期的下限,而返回值上的注释是上限。这种解释最有意义,但我有一种感觉,这也不正确,因为我认为&'a T是(有点像)一种类型,并且类型在参数与参数上具有不同的含义是没有意义的关于返回值。如果相同的符号在不同的地方有不同的含义,那么它是否会为语言添加一层杂质,即相同的语法可以有不同的含义,而我们只需要记住它在哪些地方具有不同的含义?
那么谁能解释一下生命的真正含义是什么?
作为这个问题的第二部分,我们还可以解释生命周期在结构定义中的含义吗?我也要求这个,因为我怀疑答案与函数定义不同。这是一个例子:
struct Foo<'a> {
bar: &'a i32
};
fn main() {
let foo = Foo { bar: &5 };
}
Run Code Online (Sandbox Code Playgroud)
再说一次,我们在这里似乎是在说比更foo.bar长寿。因此,结构模板参数和结构成员的生命周期又有不同的含义。'afoo'a
我听到的另一种解释是,我们说在 bar、baz 和返回值的生命周期的交点处存在一些生命周期 'a。
这似乎很合理。
您可以认为,在调用时,foo(x, y)编译器尝试用提供的参数类型来满足参数类型,如果它们兼容,则隐式“强制转换”。
您可以将较长的生命周期“转换”为较短的生命周期以进行简单引用,因此如果 z 的生命周期为 'z,编译器能够统一调用:
let z: &'z i32 = foo<'z>(x as &'z i32, y as &'z i32);
Run Code Online (Sandbox Code Playgroud)
然而,这将是一个微不足道的声明,因为很明显,在我们调用 foo 时它们都在范围内。
我不会认为这是微不足道的,但如果您将所有代码放在一个函数中来执行此范围分析,确实会更容易,并且它确实是在 C/C++ 和其他一些语言中执行的。对整个程序树执行它会花费太长时间,显式注释有助于解决这个问题。想象一个更复杂的情况:foo 内部调用 bar,bar 调用 baz,一些中间结构被创建并与这些引用一起移动,其中包括嵌套结构,如果它不仅仅是一段线性代码,而是异步的和换行的,会怎么样?未来的事情,并一路等待着他们......
返回值的注释是上限
没必要。在您的情况下, return<'a>与下限相同,但它可能与&'s self或 be'static或其他东西相关联。
如果你的函数 foo 接受像and这样的协变类型,其中- 是的,它们统一为具有下界的类型,但是如果你的函数接受像and之类的逆变类型,那么它会统一为具有上限的类型,因为现在 foo() 是负责调用,并且如果它能够为两者提供更持久的参数 - 它对两者都有效。x: &'a i32y: &'b i32'a: 'bx: Fn(&'a i32) -> ()y: Fn(&'b i32) -> ()
因此,结构模板参数和结构成员的生命周期又有不同的含义。
的声明Foo<'a>只是为字段的生命周期命名bar。当您创建 时foo,编译器会根据参数的生命周期(或范围)执行推理,并在特定情况下进行推断,例如'a = 'static,因为 5 是一个常量,有点像您能够手动编写:
let foo = Foo<'static> { &5 as &'static i32 }
Run Code Online (Sandbox Code Playgroud)
另一方面,如果您使用上一个示例中的“z”创建它,则它可能是:
let z = ...
let foo = Foo<'z> { z as &'z i32 }
Run Code Online (Sandbox Code Playgroud)
注意:无法指定本地代码块生存期来提示/强制编译器使用它们,我们只能在函数边界或类型中指定它。