Why can't I assign one dereference of a reference of a reference to another when the outer lifetimes differ?
Asked Answered
T

1

5

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?

Tetzel answered 13/4, 2020 at 19:36 Comment(0)
T
10

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
  • You cannot move out of a reference, so you also can't move the &'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).

Torbernite answered 13/4, 2020 at 20:28 Comment(2)
I've been looking for a reference link for implicit reborrows for a while now. Do you happen to have one? It doesn't seem to be mentioned in the Rust reference at all, but "because of implicit reborrows" has been the answer to a fair number of questions over the recent years.Codex
@SvenMarnach Unfortunately, if I had one, I would have linked to it (and I'd probably have a few older answers to update too!) I don't think it's in the Nomicon either.Torbernite

© 2022 - 2024 — McMap. All rights reserved.