关于理解生命周期的问题

Questions on understanding lifetimes

我一直很难理解生命周期,如果能帮助我理解这里的资源和其他 question/answers 中通常缺少的一些微妙之处,我将不胜感激。甚至本书的整个部分都具有误导性,因为它的主要示例被用作生命周期背后的基本原理或多或少是错误的(即编译器可以很容易地推断出上述函数的生命周期)。


以这个功能(有点像书上的)为例:

fn foo<'a>(x: &'a str, y: &'a str) -> &'a str {
    x
}

我的理解是显式生命周期断言 returning 引用的生命周期不应长于 xy 的最短生命周期。或者换句话说,xy 都应该比 returning 引用长寿。 (尽管我完全不确定编译器到底做了什么,它是否检查参数的生命周期,然后将最小值与 returning 引用的生命周期进行比较?)

但是,如果我们没有 return 值,生命周期意味着什么?它是否意味着特殊的含义(例如,与使用两个不同的生命周期相比?)

fn foo<'a>(x: &'a str, y: &'a str) {
    
}

然后我们有如下结构:

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

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

似乎对字段使用相同的生命周期增加了一些约束,但导致 不起作用的约束到底是什么?


这可能需要一个自己的问题,但是有很多人提到生命周期和范围是不同的但没有详细说明,是否有任何资源对此进行更深入的研究,尤其是考虑非词汇生命周期?

要了解生命周期,您必须注意它们实际上是类型的一部分,而不是值的一部分。这就是将它们指定为通用参数的原因。

也就是当你写的时候:

fn test(a: &i32) {
    let i: i32 = 0;
    let b: &i32 = &i;
    let c: &'static i32 = &0;
}

那么变量abc实际上是不同的类型:一种是&'__unnamed_1 i32,一种是&_unnamed_2 i32,另一种是是 &'static i32.

有趣的是,生命周期创建了一个类型层次结构,因此当一种类型比另一种类型寿命更长时,但除了它们相同之外,长寿命类型是短寿命类型的子类型住过一个。

特别是,在极端多重继承的情况下,&'static i32 类型是任何其他 &'_ i32 的子类型。

您可以使用此示例检查 Rust 子类型是否真实:

fn test(mut a: &i32) {
    let i: i32 = 0;
    let mut b: &i32 = &i;
    let mut c: &'static i32 = &0;
    //c is a subtype of a and b
    //a is a subtype of b
    a = c; // ok
    c = a; // error
    b = a; // ok
    a = b; // error
}

值得注意的是,生命周期是一个借用检查器问题。一旦满足并且代码被证明是安全的,生命周期将被擦除并且代码生成是盲目完成的,假设所有对内存值的访问都是有效的。这就是为什么即使生命周期是通用参数,foo<'a>() 也只实例化一次。

回到你的例子:

fn foo<'a>(x: &'a str, y: &'a str) -> &'a str {
    x
}

您可以使用不同生命周期的值调用此函数,因为编译器会推断出 'a 是两者中较短的一个,因此 &'a str 将是另一个的超类型:

    let s = String::from("hello");
    let r = foo(&s, "world");

这等同于(为生命周期注释发明的语法):

    let s: &'s str = String::from("hello");
    let r: &'s str = foo::<'s>(&s, "world" as &'s str);

关于具有多个生命周期的结构,它通常无关紧要,我通常将所有生命周期声明为相同,特别是如果类型对我的 crate 是私有的。

但是对于 public 泛型类型,声明几个生命周期可能会有用,特别是因为用户可能想要 一些 'static .

例如:

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

struct Bar<'a> {
    x: &'a i32,
    y: &'a str,
}

Foo 的用户可以将其用作:

let x = 42;
let foo = Foo { x: &x, y: "hello" };

但是 Bar 的用户必须分配一个 String,或者做一些堆栈分配的 str 魔法:

let x = 42;
let y = String::from("hello");
let bar = Bar { x: &x, y: &y };

请注意 Foo 可以像 Bar 一样使用,但不能反过来使用。

此外 impl Foo 可能会提供 impl Bar 无法提供的附加功能:

impl<'a> Foo<'a, 'static> {
    fn get_static_str(&self) -> &'static str {
        self.y
    }
}