Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swapping two local references leads to lifetime error

I have two variables of type &T, x and y, which I swap locally inside a function:

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) {}

This code fails to compile, giving the following error:

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 | |     }
  | |_____^

I fail to see why it shouldn't work. x and y have different lifetimes, but why should the compiler require y to live as long as x? I am only modifying references locally inside foo and referenced objects are guaranteed to exist. Once foo returns, it doesn't matter if these x and y even existed, does it?

For larger context, I am implementing mergesort and want to swap the primary and auxiliary (temporary) arrays this way.

like image 250
kreo Avatar asked Dec 18 '18 14:12

kreo


2 Answers

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

Because of the signature of std::mem::swap:

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

T is the type of the argument to foo, which is a reference of some lifetime chosen by the caller of foo. In the 2018 edition of Rust, the latest compiler gives a slightly more detailed error message in which it calls this lifetime '1. Calling std::mem::swap requires the type of x, &'1 T, to be the same as the type of y, but it can't shrink the lifetime of x to match that of y because the lifetime of x is chosen by the caller, not by foo itself. Vikram's answer goes into more detail on why the lifetime cannot be shrunk in this case.

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

This is true, but it doesn't give you any freedom with respect to the lifetime of x inside foo. To make foo compile, you have to give the compiler another degree of freedom by making a new borrow of which the compiler can choose the lifetime. This version will compile (playground):

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

This is called reborrowing, and it happens implicitly in some cases, for example, to the receiver of a method call that takes &mut self. It does not happen implicitly in the case you presented because swap is not a method.

like image 125
trent Avatar answered Oct 15 '22 03:10

trent


It is helpful to compile this program with the latest stable toolchain on the 2018 Edition, since it improves the error message a bit:

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

What happens is:

  • the input x is a reference with the arbitrary lifetime '1 established by the caller.
  • The variable y is a reference created locally, and as such it has lifetime "shorter" than '1.

As such, you cannot pass the reference in y to x, even if it may seem safe to, because x expects something that lives at least for the lifetime indicated by the caller.

One possible solution is to create a second copy of the value behind x, and borrow that locally.

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);
    }
}
like image 26
E_net4 stands with Ukraine Avatar answered Oct 15 '22 04:10

E_net4 stands with Ukraine