Why can't I have more than one mutable reference to a value?

fn main()
{
    let mut x = 10;

    let a = &mut x;
    let b = &mut x; // This makes 'a' variable useless

    *a += 10;

    println!("{}", a);
}

I understand for strings why you can’t have more than one mutable reference but how come for integers, it is a fixed size, where as strings can have a dynamic size, so string data types are stored in the heap and integers are stored on the stack as they are fixed size. But why can’t I have more than two mutable reference for integers? What risk does it pose if we had more than two mutable reference for integers?

I believe you’ve misunderstood what ownership and borrowing tries to accomplish. It would be invalid for a structure to depend on a reference it holds staying the same, somehow become invalid, or to be able to change the value under said reference.

The fact that you need to keep a reference to unsized types could be considered a special case, where a reference of some sort is needed. Therefore references don’t care if your data is on the stack or on the heap, they just care about who owns it and when it’s going to be dropped.

Referencing my idea from above, let’s say the following:

struct Foo<'a> {
    x: &'a usize,
    y: SomeState,
}

Where SomeState is only valid for a given usize, so changing the value under x would result in an invalidation of the state.

Another example is multithreading, where if more than one thread is trying to change a value, or one is trying to write and one is trying to read, then we end up with undefined behavior, where you cannot completely accurately predict the value you will read because the other thread may not have finished writing to it yet. Therefore, the function which yield mutable references to these values must ensure the references are unique, another name for a &mut reference - a unique reference.

1 Like

If you accept those caveats and still want shared mutability, that’s what Cell is for.

3 Likes

Sorry mate, I am not too sure what do you mean by structure cause in my example I am not using structures.

With Strings I understand why we can’t have more than one mutable reference, if one mutable reference attempts to expand on the String (using push_str("some text")) the pointer address changes and the 2nd mutable reference’s pointer does not update, therefore it may end up pointing to an invalid address, do I understand this correctly?

But with integers, since the size is fixed, there is no way the pointer address can change.

Forgive me for my lack of understanding.

I am not too sure what does Foo<'a> exactly means.

You’d probably be interested in

Specifically, I like the part that starts with this quotation:

My intuition is that code far away from my code might as well be in another thread , for all I can reason about what it will do to shared mutable state.

Which applies just as well to integers as it does to any other type.

(But as cuviper said, for simple things like i32, you can opt-in to sharing like that if you want, using Cell [or atomics or …].)

4 Likes

Part of the reason is so that your code can go even faster.

Observe the following:

fn main() {
    // Create some aliasing references
    let mut x = 0;
    let r1: &mut i32 = &mut x;
    let r2: &mut i32 = unsafe { &mut *(r1 as *mut i32) };
    
    show_ub(r1, r2);
}

#[inline(never)]
fn show_ub(x: &i32, y: &mut i32) {
    let original_x = *x;
    *y = 3;
    assert_eq!(*x, original_x);
}

On debug builds, this panics. On release builds, it succeeds. Why? Because on release builds, the compiler is allowed to optimize the assert_eq! away, because it can assume that x and y don’t point to the same variable.

If you were allowed to create multiple mutable references in safe code, the compiler wouldn’t be able to do this.

5 Likes

Just because there isn’t a structure present at the moment in your code, doesn’t mean there will never be a structure in your code, so the compiler makes sure it will never break, no matter what.

Foo<'a> means that Foo is generic over any 'a, such that the Foo lives shorter than or equal to 'a. In other words, 'a is a lifetime, and since Foo contains a lifetime 'a, we need to make sure that the variable under the reference (x in this case) lives longer than the struct so that we don’t end up reading after a free – or 'a must outlive or live as long as Foo to make it never be able to read data that has been freed.

1 Like

The reason that you cannot have two &mut references to the same value is that the language designers chose to enforce this rule. Now, you might ask, why did we choose this rule? Fundamentally it allows the compiler to ensure you never have a dangling reference, and any library author know they can depend on this.

Let’s take a look at the code below:

let list = vec![0, 1, 2, 3];
let a = &mut list[0];
list.push(4);
*a = 10;

This code might resize the list, which would invalidate the a reference. Now, why does this fail? Well the expression &mut list[0] borrows list mutably, meaning that as long as a exists, you cannot have another mutable reference to list. Since push is &mut self method, it also requires a mutable reference to call it, so the borrow-checker will fail the code.

The creators of the Vec library know that the compiler acts this way when it sees a mutable reference, so they can depend on it when they write their library. It would be much harder to write this kind of code if the compiler just says “well I prevent this kind of stuff sometimes, maybe, if I want to”, since then you can’t always depend on the compiler rejecting invalid code without understanding very well when it rejects the code.

Since i32 implements Sync, it is possible to send a &mut i32 to a different thread. If the compiler also allowed you to keep a reference to it in this thread, you could suddenly have the other threads writing to your variable while you read it. This is called a data race, and is undefined behaviour, so we do not allow it.

Now, of course, if we promise not to send it to a different thread, writing to it from several places is perfectly well defined. No two threads would be writing to it at the same time, which is what caused the problem. However the way you promise the compiler not to modify it from several threads at once, is to use a type that does not implement Sync. Luckily the standard library includes the Cell type, which does not implement Sync, and it allows you to set the value using the set method. The reason this is safe is that since we don’t have Sync, it can only ever be accessed from one thread at once.

Of course, Cell is also subject to the “only one &mut reference” rule. All types are. This is why the set method is a &self method, not a &mut self method. You can call a &self method using only a immutable reference, and you can have several of those. Internally it works using what is called interior mutability.

2 Likes