Question about lifetime of dereferencing reference to reference

Hi all,

The following compiles fine:

fn immutable<'outer, 'inner>(reference: &'outer &'inner u8) -> &'inner u8 {
    *reference
}

But when I use a reference as mutable, I get a compile error:

fn mutable<'outer, 'inner>(reference: &'outer mut &'inner mut u8) -> &'inner mut u8 {
    *reference
}

Error:

error[E0623]: lifetime mismatch
 --> test.rs:2:5
  |
1 | fn mutable<'outer, 'inner>(reference: &'outer mut &'inner mut u8) -> &'inner mut u8 {
  |                                       --------------------------     --------------
  |                                       |
  |                                       this parameter and the return type are declared with different lifetimes...
2 |     *reference
  |     ^^^^^^^^^^ ...but data from `reference` is returned here

error: aborting due to previous error

What is the difference between references as immutable and references as mutable that cause immutable to compile, but cause errors when trying to compile mutable?

While I'm not entirely sure about this, I think the difference is due to & being Copy and &mut not being Copy. In immutable, &'inner can just be copied out of the dereference, so you can retain the inner lifetime. In mutable, however, it seems to be attempting a reborrow because it can't move the &'inner mut (changing the body to &mut *reference shows the same behaviour). This reborrow ends up having a lifetime of 'outer, because I think it is reborrowing through both references and using the outer lifetime as it must be the shorter (you can also make the body&mut **reference to show the same behaviour; for all these bodies, making the output lifetime 'outer removes the error).

This compiles:

fn mutable<'outer, 'inner>(reference: &'outer mut &'inner mut u8) -> &'outer mut u8 {
    *reference
}

Basically if you were to return &'inner mut u8, the result would not be considered a borrow of the outer mutable reference, and thus it would not be locked from usage until you stop using the returned reference.

However this would not be good, as you would then have two mutable references, usable at the same time, that overlap (i.e. they can both access the u8). This is against the defining characteristic of mutable references — that they are unique.

Returning a reference with 'outer instead is fine, as this is a borrow of the outer reference, leaving it unusable while the returned reference exists.

7 Likes

Thank you both for your answers!

I think both have the same point: if this implementation of mutable would be accepted by the compiler, then it would be possible to create two references as mutable to the same thing. Indeed, if I "suppress" the compile error by replacing the implementation with unimplemented!(), then the following compiles:

fn mutable<'outer, 'inner>(reference: &'outer mut &'inner mut u8) -> &'inner mut u8 {
    unimplemented!();
}

fn main() {
    let mut a = 4;
    let mut b = &mut a;
    let c = mutable(&mut b);
    // If the original implementation of `mutable` were to be accepted, both `b` and `c` are references as mutable to `a`!
    *b = 5;
    *c = 6;
}
1 Like

Yes, and alternatively you'd be able to give the same reference to mutable twice, which would even more obviously be two overlapping mutable referenes.