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

时间:2018-12-18 14:56:58

标签: rust lifetime borrow-checker borrowing

我有两个&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是否甚至存在都没关系吗?

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

4 个答案:

答案 0 :(得分:8)

  

很显然,xy具有不同的生存期,但是为什么编译器需要y才能生存到x呢?

由于std::mem::swap的签名:

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

Tfoo的参数类型,它是foo 的调用者选择的某些生存期的引用。在2018年版本的Rust中,最新的编译器给出了更为详细的错误消息,其中将其称为此生存期'1。调用std::mem::swap要求x的类型&'1 Ty的类型相同,但是不能缩短x的寿命匹配y的生存期,因为x的生存期是由调用方选择的,而不是foo本身。 Vikram's answer详细介绍了为何在这种情况下无法缩短生存期。

  

我实际上只是在foo内部本地修改引用,并且被引用的对象一定会存在

这是真的,但是在xfoo的生存期内,它没有给您任何自由。要使foo进行编译,必须通过重新借用编译器可以选择生存期的方法来赋予编译器另一个自由度。此版本将编译(playground):

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

这称为借入,在某些情况下,它会隐式发生for example, to the receiver of a method call that takes &mut self。在您呈现的情况下,它不会隐式发生,因为swap不是方法。

答案 1 :(得分: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);
    }
}

答案 2 :(得分: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的{​​{1}}不会缩小到函数调用的范围,因此,现在也希望&'a T的{​​{1}}是{{1} },这是不可能的,因为y超出了&'local y_owned,因此它抱怨&'a y_owned的寿命不足。

有关更多信息,请参阅this

答案 3 :(得分:3)

引用的生存期信息是其类型的一部分。由于Rust是一种静态类型的语言,因此引用变量的生存期无法在运行时动态更改。

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