Question about reading and modifying mutable references

I have a question about some nuance of mutable references that I'm not sure I'm getting right and I hope you can help me.

It is related to this example:

fn main() {

    let mut x = false;
    let y =  &mut x;
    *y = !*y;
    
    println!("{}", x);
}

Which is perfectly fine, but this other one:

fn main() {

    let mut x = false;
    let y =  *&mut x;
    y = !y;
    
    println!("{}", x);
}

results in the following error:

error[E0384]: cannot assign twice to immutable variable `y`
 --> src/main.rs:5:5
  |
4 |     let y =  *&mut x;
  |         -
  |         |
  |         first assignment to `y`
  |         help: consider making this binding mutable: `mut y`
5 |     y = !y;
  |     ^^^^^^ cannot assign twice to immutable variable

I'm guessing that the first example is fine because in the line *y = !*y;, the right-hand side of the = sign is executed first, returning the value false and then assigns the value to the left-hand side of the =. This way there's no point in time in which the mutable reference y is accessed twice.

However I don't understand the error in the second example. It seems that I'm assigning twice to y, but I don't see how. Furthermore, if I make y mutable, as the error suggests, the code works, but it doesn't change the value of x, which I also don't understand.

Hint: the type of y is bool in the second code block.

1 Like

This declares y as an immutable variable, initialized with a copy of x. (*&mut value is pointless: dereferencing and referencing cancel each other out, and you end up with just value as the result of that expression.)

2 Likes

A reference is actually an address of a buffer. Your buffer is x. In first example, you assign address of x to y and then access x by dereferencing y. In second example, you assign value of x to y, not the address. This means that y is not “connected” to x anymore. The error occurs because variables are actual buffers in the memory, not a compile-time abstraction, so

let y = *&mut x;
y = !y;

is the same as

let y = x;
y = !y;

. Your code isn’t adequate to

*&mut x = !x;

so y is just an immutable variable with the same value as x.

Hopefully, I helped you and didn’t make everything even harder to understand :sweat_smile:

1 Like

This one:

    let mut x = false; // 1
    let y =  &mut x;   // 2
    *y = !*y;          // 3

Goes like:

    x             y               x             y               x
1) +-------+  2) +-----------+   +-------+  3) +-----------+   +-------+
   | false |     | (memory   |-->| false |     | (memory   |-->| true  |
   +-------+     |  address) |   +-------+     |  address) |   +-------+
                 +-----------+                 +-----------+

You modify x through the y, which is a &mut bool. You never change y itself. If you tried to assign to y again (in contrast with *y), like y = &mut true, you would get a similar error about assigning twice.

Where as this (if it compiled)...

    let mut x = false; // 1
    let y =  *&mut x;  // 2
    y = !y;            // 3

is:

    x             y           x             y               x
1) +-------+  2) +-------+   +-------+  3) +-------+   +-------+
   | false |     | false |   | false |     | true  |   | false |
   +-------+     +-------+   +-------+     +-------+   +-------+

And you're modifying y itself (or rather, attempting to, but cannot as y was not declared as mutable), which is a bool this time.

5 Likes

Thanks a lot, all responses were very helpful to understand what's going on, but you pointed out something that I hadn't realized before:

This a subtle difference that I hadn't noticed. Even though y is a mutable reference it's not a mutable variable, which means it can be used to modify the mutable value it points to (in this case x) but it's value can't change (to which other variable is pointing to) right?

Right; the difference is not subtle, it's a fundamental one. Not all variables are references. You might be used to all variables being references in other languages; Rust is not one of them. Rust is more like C and C++ in this regard, and references are like pointers – you create them explicitly, by taking the address of a value, or by calling functions that return references to the interior of some object, but just because you declare a binding (variable) doesn't mean that you automatically have a reference.

Thanks for the explanation, by the subtle difference I meant that when a variable is a reference, it can be a mutable reference or an immutable reference, but that's a different thing from the variable itself being mutable or immutable. This is, there can be:

  • Immutable variables that are immutable references
  • Immutable variables that are mutable references
  • Mutable variables that are immutable references
  • Mutable variables that are mutable references

Yes, correct. And only mutable references allow you to modify another value indirectly. An immutable reference doesn't allow you to write through it (i.e. it disallows indirect mutation), and an immutable binding doesn't allow you to change what it's bound to (i.e. it disallows direct mutation). A mutable binding lets you re-assign the value (i.e., it allows direct mutation), and a mutable reference allows you to write through itself (i.e., it allows indirect mutation).

(Actually, there are examples when you can write through an immutable reference: this is called interior mutability, and it is provided by types such as Cell, RefCell and Mutex. However, this is the exception rather than the norm, and it depends on the application of a special language primitive, called UnsafeCell, which you probably shouldn't worry about just yet.)

1 Like

One more thing I'll add is that the mutability of a binding is not some innate, type-level quality of a variable. It's more like a mandatory lint. When you change let x = to let mut x = , you're just signaling that you do in fact intend to mutate the variable [1]. If you own the value, you can always just rebind it as mutable if you need to. It doesn't change the type of the variable.

Examples:

// Example 1: The compiler assumes you didn't want to mutate the `Vec`
// and doesn't let you call `truncate`, which takes a `&mut self`
fn example_1() {
    let v = vec![1, 2, 3];
    v.truncate(1); // ERROR
}

// Example 2: If you own the variable, you can always just rebind it as
// mutable anyway.
fn example_2() {
    let v = vec![1, 2, 3];
    let mut v = v;
    v.truncate(1); // compiles
}

// Example 3: Therefore, if you give away ownership of a value, it might
// get mutated -- even if you didn't declare it as mutable originally.
fn example_3a() {
    let v = vec![1, 2, 3];
    example_3b(v);
}
fn example_3b(mut v: Vec<i32>) {
    v.truncate(1); // compiles
}

In contrast, &T and &mut T are two different types. &mut T can be reborrowed as a &T, but you can never go from &T to &mut T.

You can mutate through a &mut T -- in fact, the guarantee is even stronger: for whatever lifetime you can use a &mut T, no other code can observe the reachable memory -- you have exclusive access. (A more accurate name than "mutable reference" would be "exclusive reference".)

You can only mutate through a &T if there is interior mutability -- also known as shared mutability. (A more accurate name than "immutable reference" would be "shared reference".) [2]

Example:

fn example(exclusive: &mut String) {
    // You can go from `&mut` to `&`...
    let shared: &String = exclusive;
    
    // But you can *never* go from `&` to `&mut`
    let unique: &mut String = shared;
}

  1. without using interior mutability ↩︎

  2. I agree that you can just file that away as a topic to explore later; usually when you see a shared reference, you can't mutate. ↩︎

1 Like