为单个变量分配不同的生命周期

Assigning different lifetimes to a single variable

试图理解 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 时,我分配了一个 &'a 引用给它,所以 Rust 假设 bar 有生命周期 'a。因此,当我尝试 return 类型 &'a str 的值时,它会抱怨它与 return 类型 'b str.

不匹配

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

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

相反,bar 是根据 return 类型分配的生命周期 'b。这就是为什么它不抱怨从 b 分配某些东西,而是抱怨从 a.

分配一些东西

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

首先,如果您承诺 a 至少活到 b,通过 fn get_x<'a: 'b, 'b>,整个程序编译没有问题。

其次,要查看问题是否基于类型,请检查您的代码版本: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
}

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

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

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

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

表达式 &a.x&b.x 创建新的借用引用。这些引用有自己的生命周期;我们称它们为 'ax'bx'ax 借用 a,其类型为 &'a Foo,因此 'a 必须比 'ax ('a: 'ax) 更有效 – 同样 'bx'b。到目前为止,'ax'bx 是无关的。

为了判断bar的类型,编译器必须统一'ax'bx。鉴于 'ax'bx 是不相关的,我们必须定义一个新的生命周期 'abx 作为 'ax'bx 的并集,并将这个生命周期用于两个借用(replacing/refining '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 的类型必须与 &'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
}