【问题标题】:Assigning different lifetimes to a single variable为单个变量分配不同的生命周期
【发布时间】:2021-11-15 21:59:00
【问题描述】:

我是 still 试图了解 Rust 所有权和生命周期,但我对这段代码感到困惑:

struct Foo {
    x: String,
}

fn get_x<'a, 'b>(a: &'a Foo, b: &'b Foo) -> &'b str {
    let mut bar = &a.x;
    bar = &b.x;
    bar
}

Playground

这段代码编译,因为data from 'a' is returned。我认为这是因为当我初始化 bar 时,我为其分配了一个 &amp;'a 引用,因此 Rust 假设 bar 的生命周期为 'a。因此,当我尝试返回 &amp;'a str 类型的值时,它会抱怨它与返回类型 'b str 不匹配。

我不明白的是:为什么我首先可以将&amp;'b str 分配给bar?如果 Rust 假设 bar 具有生命周期 'a,那么它不应该阻止我将 b.x 分配给它吗?

【问题讨论】:

    标签: rust lifetime


    【解决方案1】:

    每次借用都有不同的生命周期。 Rust 编译器总是试图最小化生命周期,因为较短的生命周期与其他生命周期相交的机会较小,这对于可变借用尤其重要(请记住,任何时候都只能有一个特定内存位置的活动可变借用)。从另一个借位派生的借位的生命周期可以短或等于另一个借位的生命周期,但永远不会更长。

    让我们检查一个没有任何错误的函数变体:

    fn get_x<'a, 'b>(a: &'a Foo, b: &'b Foo) -> &'b str {
        let mut bar = &a.x;
        bar = &b.x;
        todo!()
    }
    

    表达式&amp;a.x&amp;b.x 创建新的借用引用。这些引用有自己的生命周期;我们称他们为'ax'bx'axa 借用,它的类型为 &amp;'a Foo,所以 'a 必须比 'ax ('a: 'ax) 寿命更长——同样适用于 'bx'b。到目前为止,'ax'bx 是无关的。

    为了确定bar的类型,编译器必须统一'ax'bx。鉴于'ax'bx 是不相关的,我们必须定义一个新的生命周期'abx,作为'ax'bx 的并集,并将这个生命周期用于两个借用(替换/优化'ax'bx) 和bar 的类型。这个新的生命周期需要承载来自'ax'bx 的约束:我们现在有'a: 'abx'b: 'abx。生命周期为 'abx 的借用不会从函数中逃逸,而生命周期 'a'b 由于是函数的生命周期参数,因此比调用帧的寿命更长,因此满足了约束。

    现在让我们回到原来的函数:

    fn get_x<'a, 'b>(a: &'a Foo, b: &'b Foo) -> &'b str {
        let mut bar = &a.x;
        bar = &b.x;
        bar
    }
    

    这里,我们有一个额外的限制:bar 的类型必须与&amp;'b str 兼容。为此,我们必须统一'abx'b。鉴于我们有'b: 'abx,这个统一的结果就是'b。然而,我们也有约束'a: 'abx,所以我们应该把这个约束转移到'b,得到'a: 'b

    这里的问题是约束'a: 'b 只涉及生命周期参数(而不是匿名生命周期)。生命周期约束是函数契约的一部分;添加一个是 API 的重大更改。编译器可以根据函数签名中使用的类型推断出一些生命周期约束,但它永远不会推断出仅来自函数实现的约束(否则实现更改可能会默默地导致 API 中断更改)。在函数签名中显式添加where 'a: 'b 会使错误消失(尽管它使函数更具限制性,即一些在没有约束的情况下有效的调用在约束下变得无效):

    fn get_x<'a, 'b>(a: &'a Foo, b: &'b Foo) -> &'b str
    where
        'a: 'b,
    {
        let mut bar = &a.x;
        bar = &b.x;
        bar
    }
    

    【讨论】:

    • 比我做的更漂亮!并充分利用todo!()
    【解决方案2】:

    我认为根据初始分配假设 bar 被分配生命周期 'a 是不正确的。

    相反,bar 根据返回类型分配了生命周期 'b。这就是为什么它不会抱怨从 b 分配一些东西,但它确实抱怨从 a 分配一些东西。

    您可以做一些事情来进一步验证这里发生了什么。

    首先,如果您保证a 的寿命至少与b 一样长,请通过 fn get_x&lt;'a: 'b, 'b&gt;,整个程序编译没有问题。

    其次,要查看问题是否基于类型,请查看此版本的代码:Playground

    struct Foo {
        x: String,
    }
    
    fn get_x<'a, 'b>(a: &'a Foo, b: &'b Foo) -> &'b str {
        let mut bar: &'_ String = &a.x; // Alternatively try with 'a, 'b or '_ for the lifetime parameter
        bar = &b.x;
        &b.x
    }
    

    这里我们返回的东西在生命周期内绝对正确,只需使用bar 来明确指定生命周期参数。我发布的版本可以编译,因为这里借用检查器只找到包含'a'b 的生命周期。但是,如果我们将'_ 替换为'a'b,它将无法编译,因为现在当我们从不同的参数分配给同一个变量时,我们的生命周期不匹配。

    【讨论】:

    • 感谢您的回答!我同意我在这里计算寿命的方式可能是错误的,但我仍然感到困惑。编译器不会抱怨将 a 分配给 bar 的内容(没有与该行相关的错误)。并且错误消息明确表示它正在返回“来自'a'的数据”,这对我来说意味着它认为bar(我要返回的内容,以及它在错误中加下划线的行)是“来自'的数据a'",即它与a具有相同的生命周期。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-21
    • 2015-05-03
    • 1970-01-01
    相关资源
    最近更新 更多