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

7

我有两个类型为 &T 的变量,xy,我在一个函数中本地交换它们:

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的生命周期不同,但为什么编译器要求y的生命周期与x相同呢?我只是在foo内部局部修改引用,并且被引用的对象是保证存在的。一旦foo返回,这些xy是否存在就无所谓了,对吧?
更大的背景是,我正在实现归并排序,想以这种方式交换主数组和辅助(临时)数组。
4个回答

8
显然,xy有不同的生命周期,但为什么编译器要求yx一样长呢?
因为std::mem::swap的签名如此。
pub fn swap<T>(x: &mut T, y: &mut T)

T是传递给foo函数的参数类型,它是由调用foo的程序员选择的某个生命周期的引用。在Rust 2018版中,最新编译器会在稍微详细一些的错误信息中将这个生命周期称为'1。调用std::mem::swap需要x的类型&'1 Ty的类型相同,但它不能缩短x的生命周期以匹配y的生命周期,因为x的生命周期是由调用者选择的,而不是由foo本身选择的。Vikram's answer更详细地解释了为什么在这种情况下生命周期不能被缩短。

我实际上只是在foo内部局部修改引用,并保证引用的对象存在

这是正确的,但它并没有让你在 foo 中对 x 的生命周期有任何自由。为了使 foo 编译成功,你需要再借用一次,让编译器可以选择生命周期。这个版本可以编译 (playground):
pub fn foo<T: Copy>(x: &T) {
    let mut x = &*x;
    ...
}

这被称为“重新借用”,在某些情况下会隐式发生,例如对于使用&mut self的方法调用的接收者。在你提出的情况下,这种情况不会隐式发生,因为swap不是一个方法。

5

建议使用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 是本地创建的引用,因此其生命周期比 '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);
    }
}

4
可变引用与其所指的类型不变。如果你有 &'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 没有足够长的生命周期。

更多信息,请参见此处


3

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接