交换两个本地引用会导致生命周期错误

Swapping two local references leads to lifetime error

我有两个 &Txy 类型的变量,我在一个函数内本地交换它们:

pub fn foo<T: Copy>(mut x: &T) {
    let y_owned = *x;
    let mut y = &y_owned;
    for _ in 0..10 {
        do_work(x, y);
        std::mem::swap(&mut x, &mut y);
    }
}

fn do_work<T>(_x: &T, _y: &T) {}

此代码无法编译,出现以下错误:

error[E0597]: `y_owned` does not live long enough
 --> src/lib.rs:3:22
  |
3 |         let mut y = &y_owned;
  |                      ^^^^^^^ borrowed value does not live long enough
...
8 |     }
  |     - borrowed value only lives until here
  |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 1:5...
 --> src/lib.rs:1:5
  |
1 | /     pub fn foo<T: Copy>(mut x: &T) {
2 | |         let y_owned = *x;
3 | |         let mut y = &y_owned;
4 | |         for _ in 0..10 {
... |
7 | |         }
8 | |     }
  | |_____^

我不明白为什么它不起作用。 xy 有不同的生命周期,但是编译器为什么要要求 yx 一样长?我只是在 foo 中本地修改引用,并且保证引用的对象存在。一旦 foo returns,这些 xy 是否存在都没有关系,不是吗?

对于更大的上下文,我正在实现合并排序并希望以这种方式交换主数组和辅助(临时)数组。

使用 2018 版最新的稳定工具链编译此程序很有帮助,因为它稍微改进了错误消息:

error[E0597]: `y_owned` does not live long enough
 --> src/lib.rs:4:17
  |
1 | pub fn foo<T: Copy>(mut x: &T) {
  |                            - let's call the lifetime of this reference `'1`
...
4 |     let mut y = &y_owned;
  |                 ^^^^^^^^
  |                 |
  |                 borrowed value does not live long enough
  |                 assignment requires that `y_owned` is borrowed for `'1`
...
9 | }
  | - `y_owned` dropped here while still borrowed

发生的事情是:

  • 输入 x 是调用者建立的具有任意生命周期 '1 的引用。
  • 变量 y 是本地创建的引用,因此它的生命周期 "shorter" 比 '1.

因此,您不能将 y 中的引用传递给 x,即使这样做看起来很安全,因为 x 期望至少在指定的生命周期内存在来电者。

一个可能的解决方案是创建 x 后面的值的第二个副本,并在本地借用它。

pub fn foo<T: Copy>(x: &T) {
    let mut x = &*x;
    let mut y = &*x;
    for _ in 0..10 {
        do_work(x, y);
        std::mem::swap(&mut x, &mut y);
    }
}

Obviously, x and y have different lifetimes, but why should compiler require y to live as long as x?

因为std::mem::swap的签名:

pub fn swap<T>(x: &mut T, y: &mut T)

Tfoo 的参数类型,它是 foo 的调用者选择的某个生命周期 的引用.在 Rust 的 2018 版中,最新的编译器给出了一个稍微更详细的错误消息,其中调用了此生命周期 '1。调用std::mem::swap要求x&'1 T的类型与y的类型相同,但不能缩减x的生命周期匹配 y 的生命周期,因为 x 的生命周期是由 调用者 选择的,而不是由 foo 本身选择的。 更详细地说明了为什么在这种情况下不能缩短生命周期。

I am essentially only modifying references locally inside foo and referenced objects are guaranteed to exist

这是事实,但它并没有给您关于 xfoo 中的生命周期的任何自由。要使 foo 编译,您必须通过重新借用编译器可以选择生命周期的方式来为编译器提供另一个自由度。此版本将编译 (playground):

pub fn foo<T: Copy>(x: &T) {
    let mut x = &*x;
    ...
}

这称为再借,在某些情况下会隐式发生,。在您提供的情况下,它不会隐式发生,因为 swap 不是方法。

引用的生命周期信息是其类型的一部分。由于 Rust 是一种静态类型语言,引用变量的生命周期不能在运行时动态改变。

引用的生命周期 x 由调用者指定,它必须长于函数内部创建的所有内容。 y 的生命周期是函数局部变量的生命周期,因此比 x 的生命周期短。由于两个生命周期不匹配,您不能交换变量,因为您不能动态更改变量的类型,而生命周期是其类型的一部分。

可变引用在它们引用的类型上是不变的。如果你有 &'a mut T,那么它在 T 上是不变的。 swap() 的签名要求两个输入参数具有相同的类型和相同的生命周期。即它们都是对 T.

的可变引用

让我们看看你的问题:

foo() 的参数是 &T 并且生命周期为 foo<'a, T: Copy>(mut x: &'a T) 并且此生命周期由调用者指定。在该函数中,您有一个局部变量 y_owned 并且您在某个局部生命周期内引用了它。所以在这一点上我们有 &'a T 这是输入参数,生命周期由调用者设置,&'local y_owned 有一些本地生命周期。一切顺利!

接下来,您调用 swap() 并将可变引用(&mut &T&mut &y_owned)传递给上述引用。现在,问题来了;由于它们是可变引用,并且如前所述,它们在指向的内容上是不变的; x&'a T 将不会缩小到函数调用的范围内,因此 y&'local y_owned 现在也应该是 &'a y_owned,这是不可能的,因为 'a 超出了 y_owned,因此它抱怨 y_owned 活得不够长。

更多请参考this