Why does Rust allow mutation through a reference field using an immutable binding?

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:

Leave a Comment