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.
Obviously,
x
andy
have different lifetimes, but why should compiler requirey
to live as long asx
?
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.
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:
x
is a reference with the arbitrary lifetime '1
established by the caller.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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With