`&&mut S` confusions. How should I understand it better?

This question comes from the auto ref/deref feature of Rust. Namely, if you have a method fn f(&self) for a struct S, you can call s.f() no matter if s is a S, or &S, or &&&&&S. I read the dot operator in nomicon (and of course, the stackoverflow post it tries to steal from :slight_smile: ) to better understand this auto ref/deref behavior.

Now I think I (mainly?) understand how it works. However when it comes to &mut, things start to bother again. Enough background words, let me show some code below to explain my confusions in details:

struct S; // an empty struct

impl S {
    fn f(&mut self) {}
}

fn main() {
    // ok, add `&mut` auto ref
    S.f();

    // ok, perfect match
    (&mut S).f();

    // ok, add `*` deref to &mut S
    (&mut &mut S).f();

    // doesn't compile
    // error[E0596]: cannot borrow data in a `&` reference as mutable
    (&&mut S).f();
}

rustc --explain E0596 showed a seemingly irrelevant example:

let x = 1;
let y = &mut x; // error: cannot borrow mutably

After some trials I (seem to) found the relation: it's similar to the situation below (same error code):

// doesn't compile
// error[E0596]: cannot borrow data in a `&` reference as mutable
(&S).f();

My observation here is that, when derefing a &&mut S, the result is coerced (is this the right term to use here?) to &S instead of &mut S. To verify my guesses, I tried something with the primitive type i32 as follows:

fn f(x: &i32) {}

fn main() {
    let mut x: i32 = 6;
    let y = &&mut x;

    // ok
    f(y);

    // doesn't compile
    // error[E0594]: cannot assign to `**y` which is behind a `&` reference
    // and unfortunately E0594 does not have a manual containing more details
    **y = 6;

    // doesn't compile (sure)
    // error[E0599]: no method named `what` found for type `&mut i32` in the current scope
    // confusing part: it implies `*y` is `&mut i32`, no coercion or whatsoever
    (*y).what();
}

which does somewhat imply that derefing &&mut S will yield an &S instead of &mut S, meanwhile however, the last .what() trial also suggested something contradictive.

My question: how should I understand &&mut S? A shared (const) reference to an exclusive (mutable) reference to some type is definitely confusing, but at the end, it is indeed a real thing. I didn't find more explanations in the rust book or nomicon (maybe I missed some part?). Any explanations or document references are much appreciated. Thanks!

Yes a &&mut T will coerce to a &T, but never to a &mut T. This is because going from a shared reference of a unique reference of T to a shared reference of T is always safe (you only have shared access paths). But going from that to a unique reference of T can't work, because you only have a shared access path. There is no general way to know that you actually do have unique access to the underlying value.

2 Likes

Does that imply that &&mut T cannot do anything more than a &&T? If that's the case, why don't we just ban &&mut... (and anything like &...&mut...) as a concrete type? What would happen if we ban? Does &&mut has its own unique semantics so it has to show up somewhere?

To be more specific, what if we make a language rule like: taking & to an existing (possibly mutably borrowed) variable will only yield to immutably borrowed types? E.g.

let x: &mut i32 = &mut 6;
let y = &x; // type of y will be `&&i32` instead of `&&mut i32` which inherently makes no sense

Wouldn't it be pretty weird to have exceptions such as "you can have references to anything except mutable references"? What about generic code? If you have something you can write to such as a file, you can also write to a mutable reference to a file because of this blanket impl. Would that mean it should be impossible to have a &self method on that trait?

Structs might also contain mutable references, and it's nice to have shared references to those structs.

3 Likes

There's also the fact that these

fn refref1<'a, 'b>(r: &'a &'b u32) -> &'a u32 { *r } // ok
fn refref2<'a, 'b>(r: &'a &'b u32) -> &'b u32 { *r } // ok

are both perfectly fine, but

fn refmut1<'a, 'b>(r: &'a &'b mut u32) -> &'a u32 { *r } // ok
fn refmut2<'a, 'b>(r: &'a &'b mut u32) -> &'b u32 { *r } // fail

the second version here fails to compile.

2 Likes

Oh... I thought I understand lifetimes (at least the basics). Turns out I don't... after reading your examples.

fn refmut2<'a, 'b>(r: &'a &'b mut u32) -> &'b u32 { *r } // fail

Compiler said "lifetime mismatch" and I still have no clue... Do you mind to say a few more words of what's going on here? Thanks!

Also I tried the following example to understand the syntaxes here. Still figuring out what is happening...:

// compiles
// Why?
// r is a 'a ref to a 'b ref where 'a outlives 'b', then
// why this compiles? How can a reference outlives the referred?
// Why?
fn refrefhuh<'a: 'b, 'b>(r: &'a &'b u32) -> &'a u32 {
    *r
}

fn main() {
    // Ok, let's try an example.
    let x: u32 = 5;
    // starting lifetime 'b
    let y = &x; // &'b u32
    {
        // starting lifetime 'a
        let z = &y; // &'a &'b u32
        let w = refrefhuh(z); // compiles, huh???

        // I thought it is <'b: 'a> here instead of <'a: 'b>. Am I wrong?
        // How can it possibly work here?
    }
}

I feel my understanding of lifetimes is fundamentally flawed... What materials should I read to get it right? The Rust book itself seems to only have the basic stuff which doesn't explain what is happening here (or my knowledge and reasoning is not strong enough to see through it...).

'b must outlive 'a because &'a &'b u32 is nonsense otherwise, so 'b: 'a is implicitly added, plus an explicit 'a: 'b and then 'a == 'b.

If you want to mess with lifetimes use this skeleton.

fn declval<T>() -> T { panic!() }

// no arguments so no implicit bounds on lifetimes
pub fn foo<'a: 'b, 'b>() {
   let _: &'a &'b () = declval(); // error like you expected
}
2 Likes

This

fn refmut2<'a, 'b>(r: &'a &'b mut u32) -> &'b u32 { *r } // fail

fails because it would allow you to create an immutable reference to the u32 that outlasts the immutable borrow of the &'b mut u32. After the borrow of the mutable reference ends, it is, well, no longer borrowed. But it overlaps with the shared reference we just created with refmut2. This is in contradiction with the rule that says that if a mutable reference overlaps with any other reference, this other reference must borrow from the mutable reference.

1 Like

Got it. That makes sense. Thanks!

Got it. Thanks for the example!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.