关于理解生命周期的问题

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


以这个函数(有点类似于本书)为例:

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

我的理解是明确的寿命断言返回参考不应该比最短的寿命的寿命更长xy。或者换句话说,无论是xy应该活得比返回参考。(虽然我完全不确定编译器到底做了什么,它是否检查参数的生命周期,然后将最小值与返回引用的生命周期进行比较?)

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

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;
}

然后变量ab并且c实际上是不同的类型:一种类型是&'__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>()也只实例化一次。

回到你的例子:

你可以用不同生命周期的值调用这个函数,因为编译器会推导出'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 来说是私有的。

但是对于公共泛型类型,声明几个生命周期可能很有用,特别是因为用户可能想要创建其中的一些'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
    }
}


以上是关于理解生命周期的问题的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>