14
votes

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);
}
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

1 Answers

12
votes

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: