令人费解的借用检查器消息:"lifetime mismatch"

Perplexing borrow checker message: "lifetime mismatch"

我最近遇到了一条我从未见过的借用检查器消息,我正在努力理解它。这是重现它的代码(简化的现实生活中的例子更复杂)- playground:

fn foo(v1: &mut Vec<u8>, v2: &mut Vec<u8>, which: bool) {
    let dest = if which { &mut v1 } else { &mut v2 };
    dest.push(1);
}

编译失败,出现以下错误:

error[E0623]: lifetime mismatch
 --> src/main.rs:2:44
  |
1 | fn foo(v1: &mut Vec<u8>, v2: &mut Vec<u8>, which: bool) {
  |            ------------      ------------ these two types are declared with different lifetimes...
2 |     let dest = if which { &mut v1 } else { &mut v2 };
  |                                            ^^^^^^^ ...but data from `v2` flows into `v1` here

...接着是另一个关于从 v1 流入 v2 的数据。

我的问题是:这个错误是什么意思?什么是数据流?假设代码仅将 Copy 数据推送到其中一个变量,它是如何在两个变量之间发生的?

如果我遵循编译器并强制 v1v2 的生命周期匹配,函数编译 (playground):

fn foo<'a>(mut v1: &'a mut Vec<u8>, mut v2: &'a mut Vec<u8>, which: bool) {
    let dest = if which { &mut v1 } else { &mut v2 };
    dest.push(1);
}

然而,进一步检查发现原始代码不必要地复杂,是 v1v2 实际 Vec 时留下的,而不是引用。一个更简单和更自然的变体是将 dest 设置为不是 &mut v1&mut v2,而是更简单的 v1v2,它们是开始的引用.这也编译 (playground):

fn foo(v1: &mut Vec<u8>, v2: &mut Vec<u8>, which: bool) {
    let dest = if which { v1 } else { v2 };
    dest.push(1);
}

在这个看似等效的公式中,v1v2 匹配的生命周期不再是必需的。

你这里的是这个错误程序的变体:

fn foo(x: &mut Vec<&u32>, y: &u32) {
  x.push(y);
}

错误消息过去有点模糊,但如果您有兴趣,可以更改为 pull request. This is a case of Variance, which you can read more about in the nomicon。这是一个复杂的主题,但我会尽力解释它的快速和简短。

除非您指定生命周期,否则当您从 if 语句中 return &mut v1&mut v2 时,编译器会确定这些类型具有不同的生命周期,因此 return换一种类型。因此,编译器无法确定 dest 的正确生命周期(或类型)。当您显式地将所有生命周期设置为相同时,编译器现在可以理解 if 语句 return 的两个分支具有相同的生命周期,并且可以计算出 dest.

的类型

在上面的示例中,x 的生命周期与 y 不同,因此类型不同。

问题是 &'a mut TTinvariant

首先,让我们看一下工作代码:

fn foo(v1: &mut Vec<u8>, v2: &mut Vec<u8>, which: bool) {
    let dest = if which { v1 } else { v2 };
    dest.push(1);
}

v1v2 的类型省略了生命周期参数。让我们把它们明确化:

fn foo<'a, 'b>(v1: &'a mut Vec<u8>, v2: &'b mut Vec<u8>, which: bool) {
    let dest = if which { v1 } else { v2 };
    dest.push(1);
}

编译器必须找出dest的类型。 if 表达式的两个分支产生不同类型的值:&'a mut Vec<u8>&'b mut Vec<u8>。尽管如此,编译器还是能够找出一种与两种类型都兼容的类型;我们称这种类型为 &'c mut Vec<u8>,其中 'a: 'c, 'b: 'c&'c mut Vec<u8> 这里是 &'a mut Vec<u8>&'b mut Vec<u8> 的共同 supertype,因为 'a'b 都比 'c 长寿(即 'c'a'b) 的生命周期 shorter/smaller。

现在,让我们检查错误代码:

fn foo<'a, 'b>(v1: &'a mut Vec<u8>, v2: &'b mut Vec<u8>, which: bool) {
    let dest = if which { &mut v1 } else { &mut v2 };
    dest.push(1);
}

同样,编译器必须找出 dest 的类型。 if 表达式的两个分支分别产生类型的值:&'c mut &'a mut Vec<u8>&'d mut &'b mut Vec<u8>(其中 'c'd 是新的生命周期)。

我之前说过 &'a mut TT 上是不变的。这意味着我们无法通过更改 &'a mut T 中的 T 来生成 &'a mut T 的子类型或超类型。这里,T 类型是 &'a mut Vec<u8>&'b mut Vec<u8>。它们不是同一类型,因此我们必须得出结论,类型 &'c mut &'a mut Vec<u8>&'d mut &'b mut Vec<u8> 是不相关的。因此,dest.

没有有效类型