每次借用都有不同的生命周期。 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 的并集,并将这个生命周期用于两个借用(替换/优化'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
}