I want to write the following function:
fn foo<'a, 'b, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
*rr1 = *rr2;
}
But the compiler complains:
error[E0623]: lifetime mismatch
--> src/lib.rs:2:12
|
1 | fn foo<'a, 'b, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
| ----------- ------------------- these two types are declared with different lifetimes...
2 | *rr1 = *rr2;
| ^^^^ ...but data from `rr2` flows into `rr1` here
My mental model of Rust's lifetimes does not agree that the code is wrong. I read the type of rr2
as "A reference with lifetime 'b
to a reference with lifetime 'c
to an u32
". Thus when I dereference rr2
, I get a reference with lifetime 'c
to an u32
. This should be safe to store in *rr1
, which has the same type.
If I require that 'b
outlives 'c
, it works:
fn foo<'a, 'b: 'c, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
*rr1 = *rr2;
}
This makes me think that the type &'b mut &'c mut u32
means the u32
at the end of the reference chain is only available during the intersection of 'b
and 'c
.
What is the right explanation for Rust's behavior here? And why do references of references behave this way instead of the way I thought they do?
You cannot dereference a &'b mut &'c mut u32
and get a &'c mut u32
because:
&mut
references are not trivially copiable, so you can't copy the &'c mut u32
; and&'c mut u32
(which would leave the outer reference dangling).Instead, the compiler reborrows the u32
with the outer lifetime, 'b
. This is why you get an error message that data from rr2
flows into rr1
.
If foo
were allowed to compile, you could use it to get two &mut
references to the same u32
, which is forbidden by the rules of references:
let (mut x, mut y) = (10, 20);
let mut rx = &mut x;
let mut ry = &mut y;
foo(&mut rx, &mut ry); // rx and ry now both refer to y
std::mem::swap(rx, ry); // undefined behavior!
If I require that 'b outlives 'c, it works
Because 'c
must already outlive 'b
¹, if you require that 'b
also outlives 'c
, it follows that 'c
= 'b
. The updated signature is equivalent to this:
fn foo<'a, 'b>(rr1: &'a mut &'b mut u32, rr2: &'b mut &'b mut u32)
That is, you have unified 'c
and 'b
, and now there's no problem borrowing a &'b mut u32
from rr2
because the inner and outer lifetimes both live for 'b
. However, the compiler now won't let you write the broken code in the example I gave earlier, since ry
is already borrowed for its entire lifetime.
Interestingly, if you make the inner reference non-mut
, it also works:
fn foo<'a, 'b, 'c>(rr1: &'a mut &'c u32, rr2: &'b mut &'c u32) {
*rr1 = *rr2;
}
This is because &
references are Copy
, so *rr2
is not a reborrow, but actually just a copy of the inner value.
For more information, read:
¹ It might not be obvious why 'c
outlives 'b
when there is no explicit 'c: 'b
bound. The reason is because the compiler assumes that the type &'b mut &'c mut u32
is well-formed. Well-formedness can become complex (see RFC 1214) but in this case it just means you can't have a reference that's valid for longer ('b
) than the thing it references ('c
).
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