Confusion with &mut inside an & reference

I’m trying to pass a “context” parameter that contains some refs or mutable refs, and I’m getting confused by when the language allows me to use a mutable ref. An example, in this code, you can modify the Thing through a &mut Thing of course, but also through a non-mutable Ctx that contains the &mut Thing. However, if I have a & shared ref to that same Ctx, it doesn’t like it.

What is the mental model here? Why is the first allowed and the second disallowed?

#[derive(Debug, Default)]
struct Thing {
    num: u32
}
struct Ctx<'a> {
    thing: &'a mut Thing
}

impl Thing {
    fn change(&mut self) { self.num += 1; }
}

fn faa(mr: &mut Thing) {
    mr.change();
}
fn fee(ctx: &Ctx) {
    ctx.thing.change();  // Not allowed!
}


fn main() {
    let mut thing = Thing::default();
    thing.change();       // Works, of course.
    let mr = &mut thing;
    mr.change();          // Works, even though `mr` is not `mut`
    faa(mr);              // Works, you can pass it.
    let ctx = Ctx { thing: mr };
    ctx.thing.change();   // Works, even though `ctx` is not `mut`

    // But...
    let ctx_ref = &ctx;
    ctx_ref.thing.change();   // Not allowed
    fee(&ctx);

    println!("thing: {:?}", thing);
}

The error:

error[E0596]: cannot borrow `*ctx.thing` as mutable, as it is behind a `&` reference
  --> learning/rust_lang/examples/mut_refs.rs:20:5
   |
20 |     ctx.thing.change();
   |     ^^^^^^^^^ `ctx` is a `&` reference, so it cannot be borrowed as mutable

&mut guarantees to be an exclusive reference. When you can use it, you have a guarantee that nothing else in the entire program could possibly have access to the same &mut.

But when you make a shared reference to it & &mut then you get copyable shared reference that could be used from multiple places. It's no longer strictly exclusive guarantee.

Because of this sharing, you lose ability to use &mut as exclusive and effectively you get & & instead of & &mut.

If you want shared mutability, you need a type like &Mutex or Arc<Mutex> (Arc is a shared reference too, but not temporary).

3 Likes

Wow, when you explain it as “exclusive” and “shared” instead of “mutable”, suddenly it’s crystal clear. Thanks.

In this case I don’t want shared mutability, but I want to pass some mutable refs down to other functions in a convenient way. They may all be mutating, but one at a time. So I guess I need to pass the Ctx as a moved value, or as a &mut. The latter now makes sense - I’m not mutating Ctx, but I’m saying there is only one of these.

1 Like

For passing down to functions you need reborrowing.

For &mut T Rust does it magically for you. Unfortunately for Ctx(&mut T) it's not automatic, but you can implement it manually by making another Ctx with reborrowed references inside. Or keep passing &mut Ctx if you can.

1 Like

&T and &mut T are two very different types. &T is Copy, &mut T is not; &T is a shared borrow, &mut T is an exclusive borrow; etc. That's what the topic is mostly about so far and it's been covered well.


In contrast, mut bindings -- putting mut in front of a variable name when you declare it -- does not change they type of anything. Instead it expands what you are allowed to do with the variable:

  • You can overwrite it (and reinitialize it)
  • You can take a &mut _ to it

For any compiling program, you could change every non-mut binding to be a mut binding, and your program would still compile with the same semantics. (Though you would get a ton of warnings about unneeded mut bindings.) That is very not-true for changing every & to &mut.

With that context, let's look at some of the comments in the code:

    let mut thing = Thing::default();
    thing.change();       // Works, of course.
    let mr = &mut thing;
    mr.change();          // Works, even though `mr` is not `mut`
    faa(mr);              // Works, you can pass it.

You were allowed to call thing.change() (which implicitly creates a &mut thing) and make a &mut thing for mr because you declared thing as mut. If you hadn't, you wouldn't have been able to.

But you never overwrite or take a &mut to mr itself. So there is no need for mr to be declared as mut. If you wanted to overwrite mr to point at a different Thing at some point, you would need to declare it mut.

Or in other words, using or reborrowing a &mut _ is not one of the actions gated on having a mut binding.

And this applies if the &mut _ is a field in some struct, too:

    let ctx = Ctx { thing: mr };
    ctx.thing.change();   // Works, even though `ctx` is not `mut`

You didn't need to overwrite ctx, you didn't need to take a &mut _ to ctx, and you didn't need to do those things to any field of ctx either. So ctx didn't need to be a mut binding.

2 Likes