【问题标题】:Questions on understanding lifetimes关于理解寿命的问题
【发布时间】:2021-10-16 04:20:39
【问题描述】:

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


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

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

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


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

【问题讨论】:

    标签: rust lifetime


    【解决方案1】:

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

    也就是说,当你写的时候:

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

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

    有趣的是,生命周期创建了一个类型层次结构,因此当一个类型的生命周期比另一种类型的生命周期长,但除了它们是相同的,那么长生命周期的就是短生命周期的子类型.

    特别是在极端多重继承的情况下,&amp;'static i32 类型是任何其他 &amp;'_ 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&lt;'a&gt;() 也只会实例化一次。

    回到你的例子:

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

    您可以使用不同生命周期的值调用此函数,因为编译器会将'a 推断为两者中的较短者,因此&amp;'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
        }
    }
    

    【讨论】:

    • 谢谢。那么是不是只能将子类型强制转换为基本/超类型?那么当它实际上是一个超类型时,返回值如何满足foo&lt;'a&gt;(x: &amp;'a str, y: &amp;'a str) -&gt; &amp;'a str 中的'a 呢?返回值甚至不需要是超类型,因为 `&'static' (子类型)也将是有效的返回类型。除此之外,我们似乎还涉及下限,因为我们无法返回对局部函数变量的引用。因此,尽管为了避免悬空引用,这些都是有意义的,但我仍然不确定编译器正在检查的是什么对于生命周期参数。
    • @zareami10: 当你做foo&lt;'a&gt;(x: &amp;'a str, y: &amp;'a str) -&gt; &amp;'a str all x, y 并且返回值是相同的类型,所以你可以简单地做return xreturn y。类型转换是在调用站点完成的,正如我在我的段落中使用发明的语法所描述的那样。
    • @zareami10:您可以返回&amp;'static str,因为该类型是&amp;'a str 的子类型。但是您不能仅仅因为它不是&amp;'a str 的子类型而返回指向局部变量&amp;_local str 的指针。
    • @zareami10: Lifetime 是一个值存在的时间,但它也是一个引用类型的属性,它限制了它可以引用的内容。 foo() 函数不知道 xy 指向的值的确切生命周期,但它知道它们至少存在 'a。那是因为xy的类型&amp;'a i32。生命周期规则确保foo 的任何实例化以及此类实例化的任何使用都是安全的。
    • 是的,foo() 可以做return "a static value",但是&amp;'static str 在调用者框架中被转换为&amp;'a str,所以无论如何它不能使用它超过'a
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多