fishbone
fishbone

Reputation: 11

Why can't I update a value through an immutable reference of a mutable reference of a mutable value?

I found the following hard to understand:

fn main() {
    let mut x: i32 = 10;
    {
        let y: &mut i32 = &mut x;
        *y += 10;
        println!("y={}", *y);
        let z: &&mut i32 = &y;

        // z += 10; // error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut i32`
        // *z += 10; // binary assignment operation `+=` cannot be applied to type `&mut i32`
        // **z += 10; //cannot assign to data in a `&` reference
    }
    println!("x={}", x);
}

When I include *z += 10, the error message is:

error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut i32`
  --> src/main.rs:10:9
   |
10 |         *z += 10; // binary assignment operation `+=` cannot be applied to type `&mut i32`
   |         --^^^^^^
   |         |
   |         cannot use `+=` on type `&mut i32`
   |
   = help: `+=` can be used on 'i32', you can dereference `*z`: `**z`

which is exactly the same as y += 10;

Since *z has the type &mut i32, which is the same as y, why can *y be used to update the value of x, but **z can't?

Upvotes: 1

Views: 1550

Answers (2)

trent
trent

Reputation: 28075

mut is short for unique

There's a blurry line between "mutable" and "unique", but "mutable" may lead to the wrong intuition in this case. &mut references are really unique references: they cannot be aliased. If you have a &mut T, you know that while the reference exists, the T will not be accessed (either to mutate or just to read) through any other reference.

(Although you normally need a unique reference to mutate a value, there are references that allow both aliasing and mutation. &Cell<T> is one: you don't need unique access to a Cell to mutate its contents. &mut references are always unique.)

The compiler may use the knowledge that a &mut reference cannot be aliased to perform optimizations. The Aliasing section of the Rustonomicon has some more details.

& references are shared references

& references, on the other hand, can always be aliased by other & references. Anything that requires unique access to a T must guarantee that no other reference can be used to access the T. But a &&mut T can't guarantee that, because it could be aliased by another &&mut T -- exclusive access to the T is not preserved. But you can still use a &&mut T to get a regular &T, because that doesn't require unique access to the &mut T.

Naturally, this is all enforced by Rust's type system. Consider how Deref and DerefMut are defined:

  1. Deref::deref takes &self and returns &Self::Target. So you don't need unique access to self to get shared access to *self.
  2. DerefMut::deref_mut takes &mut self to return &mut Self::Target. So you do need unique access to self to get unique access to *self.

And one more thing prevents you from getting a &mut T by simply dereferencing a &&mut T:

  1. &mut references don't implement Copy.

Upvotes: 5

Tim Diekmann
Tim Diekmann

Reputation: 8486

You missed a few mut:

fn main() {
    let mut x: i32 = 10;
    {
        let mut y: &mut i32 = &mut x;
        *y += 10;
        println!("y={}", *y);
        let z: &mut &mut i32 = &mut y;
        println!("z={}", z); // output: z=20
        println!("*z={}", *z); // output: *z=20
        println!("**z={}", **z); // output: **z=20

        **z += 10;
    }
    println!("x={}", x);
}

playground

  • you want y to be mutable -> let mut y.
  • you want &y to be mutable -> ... = &mut y.
  • you want to assign it to a &mut z and reference this as mutable => let z: &mut &mut i32 = ...

I think it's more intuitive when omiting the type:

fn main() {
    let mut x = 10;
    {
        let mut y = &mut x;
        *y += 10;
        println!("y={}", *y);
        let z = &mut y;

        **z += 10;
    }
    println!("x={}", x);
}

playground

Upvotes: -1

Related Questions