15

If I have an immutable variable bound to a struct, Rust will generally not allow me to mutate the fields of the struct, or the fields of owned child structs.

However, if the field is a mutable reference, Rust will allow me to mutate the referred-to object despite my binding being immutable.

Why is this allowed? Is it not inconsistent with Rust's normal rules for immutability?

Rust won't let me do the same through an immutable reference, so an immutable reference has different behavior than an immutable binding.

Code Example:

struct Bar {
    val: i32,
}

struct Foo<'a> {
    val: i32,
    bar: Bar,
    val_ref: &'a mut i32,
}

fn main() {
    let mut x = 5;

    {
        let foo = Foo { 
            val: 6, 
            bar: Bar { val: 15 },
            val_ref: &mut x
        };

        // This is illegal because binding is immutable
        // foo.val = 7;

        // Also illegal to mutate child structures
        // foo.bar.val = 20;

        // This is fine though... Why?
        *foo.val_ref = 10;

        let foo_ref = &foo;

        // Also illegal to mutate through an immutable reference
        //*foo_ref.val_ref = 10;
    }

    println!("{}", x);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
something_clever
  • 806
  • 1
  • 7
  • 17
  • See also: [What's the difference between placing “mut” before a variable name and after the “:”?](https://stackoverflow.com/q/28587698/155423) – Shepmaster May 02 '18 at 00:26
  • 1
    @Shepmaster Indeed that article is similar - Although my question is more focused on why `*foo.val_ref = 10;` is acceptable, not why the other one's aren't. From my POV, it seems like none of the things in my example should be acceptable through an immutable binding. – something_clever May 02 '18 at 01:09

1 Answers1

13

A short way to explain this is that mutability in references and mutability in variables are orthogonal to each other. The two forms of mutability are related in the sense that we can only mutable borrow something from a mutable variable (or binding). Other than that, each binary combination is possible in Rust:

               reference mutability
            -----------------------------
variable   |     x: &T  |      x: &mut T |
mutability |------------+----------------|
           | mut x: &T  |  mut x: &mut T |
            -----------------------------

We can think of many samples of code exemplifying what can be done with such a variable x. For instance, an immutable variable of a mutable reference can modify one other element, but not itself:

let mut a = 5;
let mut b = 3;
let x: &mut i32 = &mut a;

*x = 10; // ok

x = &mut b; // nope! [E0384]
*x = 6;

Even as a field in a struct, this does not conflict with Rust's safety guarantees. If a variable is immutably bound to a struct value, each of the fields will be immutable as well. In this example:

let mut x = 5;
let foo = Foo { 
    val: 6, 
    bar: Bar { val: 15 },
    val_ref: &mut x
};
*foo.val_ref = 10;

No mutations were applied to foo here: foo.val_ref still points to x. The former can be mutated because it's mutably borrowed. References are borrow-checked independently. The lifetime parameter 'a in Foo enables the compiler to keep track of the borrow.

That second example (shown below) does not work, because from a &Foo, we can only retrieve references to its fields (such as to val_ref: &mut i32). In turn, to prevent aliasing, a &&mut i32 can only be coerced to &i32. One cannot borrow data mutably through an immutable reference.

let foo_ref = &foo;
*foo_ref.val_ref = 10; // error[E0389]

Rust won't let me do the same through an immutable reference. So an immutable reference has different behavior than an immutable binding.

Exactly!

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • 5
    [This is covered in the first edition of the book](https://doc.rust-lang.org/book/first-edition/mutability.html), specifically: `y is an immutable binding to a mutable reference, which means that you can’t bind y to something else (y = &mut z), but you can mutate the thing that’s bound to y (*y = 5). A subtle distinction.` – Simon Whitehead May 01 '18 at 22:53
  • Thanks! I am curious then about why my last example, `*foo_ref.val_ref = 10;`, is invalid though? In that case, I have an immutable reference to a struct, and I'm using it to read a mutable reference to a different object. Why is that not okay if the other situation with the immutable binding is? Either way I'm not modifying the data of the immutable structure. – something_clever May 01 '18 at 23:43
  • (I guess my overall confusion is that it seems inconsistent - It seems like mutability is transitive in some cases and not in others) – something_clever May 01 '18 at 23:45
  • @something_clever Indeed, mutability is, in a way, inherited through a reference: from `&Foo` we can only retrieve immutable references to its fields, and a `&&mut T` becomes a `&&T`. I can update the answer with this concern tomorrow. – E_net4 May 02 '18 at 00:07
  • @E_net4 So I wonder why the decision was made for mutability to **not** be inherited through bindings. In my example, my feeling is actually that Rust shouldn't accept any of them. That is - If my binding is immutable I shouldn't be able to transitively mutate references, even if I have them borrowed mutably. – something_clever May 02 '18 at 01:06
  • @E_net4 Sorry to keep peppering you with questions, but you're helping me understand a lot better :) Something I also find kind of weird though is that the immutability **is** transitive to the non-reference members of the struct, even recursively (I can't modify a field-of-a-field-of-a-field of my immutable binding). And that doesn't involve re-targeting the immutable variable... – something_clever May 02 '18 at 01:23
  • @something_clever I have updated the answer with those concerns. – E_net4 May 02 '18 at 13:27
  • Thank you. Again follow-up question though - I see now why it's technically 'safe' to retrieve a mutable reference from an immutable binding, but is it 'correct' to allow it? Because it's technically 'safe' as well for me to mutate an i32 through an immutable binding, since I have exclusive ownership and I'm not re-targeting the binding. The reason we even have immutable bindings is for semantics, not safety. – something_clever May 02 '18 at 14:14
  • @something_clever That is indeed use case dependant and potentially opinion based. Users with a functional programming background, for instance, might prefer moving content from one immutable binding to another instead of mutating a value in a single variable. Rust allows both, but leaves those decisions to the programmer. – E_net4 May 02 '18 at 14:37