Rust 函数/结构定义中 lifttimes 的字面含义是什么?

A. *_*man 10 lifetime rust

我一直在努力寻找 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)

这编译并运行良好。在这里xy, 和z都活了不同的时间长度。基础数据mn也存在不同的时间。很明显,当我们声明参数和footo 的返回值都具有生命周期时'a,我们并不是说它们都在程序的完全相同的一段时间内生存。(我尽量不说“一生”,抱歉措辞尴尬。)

我听到的另一种解释是我们说在 、 和返回值的生命周期的交点处存在一些生命周期'a。然而,这将是一个微不足道的声明,因为显然它们都在我们调用时的范围内。barbazfoo

在我看来,我们所说的是,存在某种生命周期,'a使得barbaz超过'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

bat*_*str 1

我听到的另一种解释是,我们说在 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)

注意:无法指定本地代码块生存期来提示/强制编译器使用它们,我们只能在函数边界或类型中指定它。