What is meaning of mut in double references?

I'm reading the tutorial and try to test some code. In code below, I just want to change the value of a and keep b immutated.

let mut a: i32 = 1;
let b: &mut i32 = &mut a;
let x: &&mut i32 = &b;
**x = 2;

Error is

error[E0594]: cannot assign to `**x`, which is behind a `&` reference
 --> test.rs:5:5
  |
5 |     **x = 2;
  |     ^^^^^^^ `x` is a `&` reference, so the data it refers to cannot be written
  |
help: consider changing this to be a mutable reference
  |
4 |     let x: &&mut i32 = &mut b;
  |                         +++

error: aborting due to 1 previous error

Actually, what I want to do is similiar to c++ code like

int a = 1;
int * const b = &a;
int * const * x = &b;
**x = 2;

You can't do that in Rust using references. A shared ref & won't allow mutating through it, unless what is behind the reference has interior mutability (Cells, etc). So in your example you have to use an exclusive &mut reference to b.

        let mut a: i32 = 1;
        let mut b: &mut i32 = &mut a;
        let x: &mut &mut i32 = &mut b;
        **x = 2;

You'll probably be asking why. I can't give you the PL theory explanation. But my intuition is that, if you could mutate through a & ref and change a value with a nested &mut ref, this would effectively allow multiple exclusive references to the value, since the outer & ref is sharable. So this would break the fundamental "mutable xor shared" rule.

2 Likes

Actually I was thinking another question.
In such code

let mut x: Box<i32> = Box::new(1);
*x = 2;

we use mut to let x be mutable, but what we change is *x, why isn't it be like

let x: mut Box<i32> = Box::new(1);

It’s indeed a common occurrence in Rust that you cannot do this, and end up needing to mark a reference (or some comparable struct) as mutable even though really you’re only ever mutating its target.

In fact, the way how actual ordinary references can work without this is more of an exception than a rule. While you can do something like

let b: &mut i32 = …;
// … now, mutate *b …

just fine, with other reference-like handles, it usually won’t work. For example, if you lock a Mutex, you get a MutexGuard, but if you assign that, you’ll quickly find yourself forced to mark the variable holding that MutexGuard as mutable, too.


One could say, that’s a bit of a lack of expressivity, compared to other programming languages. On the other hand, I’d argue that a “deep” notion of immutability vs. mutability is often actually quite useful.

For example, I generally do find all those dynamic languages out there a bit confusing where you mark your variables as immutable but then be like “oh, now this contains an object, so of course the fields/properties of this object are still totally mutably anyways!”.

3 Likes

There are mutable bindings (variables) and mutable references -- these are two different things.

This is a mutable binding (note there are no references):

let mut x = 3;
x = 4;

If I remove the mut, I can't change x.

But here, y contains a mutable reference:

let mut x = 3;
let y = &mut x;
*y = 4;

I wouldn't be able to change x through y unless I used a mut reference. In addition, the compiler won't let me add the mut reference unless I make x a mut binding, since without x having a mut binding, I can't change x.

Here I've made y a mut binding containing a mut reference.

let mut x = 3;
let mut y = &mut x;
*y = 4;
let mut z = 1;
y = &mut z;

This allows me to change y to refer to z. I wouldn't be able to do that unless I made y a mut binding.

1 Like

mut is a keyword in Rust that has a few very distinct functionalities; mainly 2 you’ve come across so far.[1] The one it has in

let mut x: Box<i32> = Box::new(1);

is marking the mutability of a local variable. This is something that has no effect on the type of the variable x.

It does control whether (ordinary)[2] mutation can happen to the variable x (and everything that it owns): essentially re-assigning or taking a mutable reference to x will be prohibited without that mut. Some people just consider this kind of feature more of a very strong kind of “lint”, by the way. It doesn’t make the Box itself somehow different. In fact, if you have

let x: Box<i32> = Box::new(1);

you can just go on and still do

let mut y = x;
// now, mutate *y

i.e. move ownership off to another, now mutable variable, then mutate the box.


On the other hand, the keyword mut is also used in the types of references. There is the type &mut T of “mutable exclusive references to T” and &T or “immutable shared references to T”. These are simply completely distinct types.

Now in your pseudo-code

let x: mut Box<i32> = Box::new(1);

you tried to put a mut into a type involving Box, however mutability is never part of types; except that it’s a keyword used in the name of the &mut T type.[3] It doesn’t make sense where you put it. [Neither would e.g. Box<mut i32> or so make any sense.]


The simple answer is: support for annotating mutability of local variables is limited to the level of annotating the whole variable. That’s all Rust supports.

(Fun fact, temporary variables, i.e. the places where intermediate results from computations are stored, are always mutable. Hence, e.g. something like vec![1].push(2) compiles fine, though it’s somewhat of a useless example, because it modifies the Vec in a temporary variable, which is then necessarily immediately dropped before it could ever be used for anything else again.)


  1. A third use would be in the type *mut T or raw mutable pointers; a fourth in the usage of ref mut … patterns, a rarely used feature nowadays. ↩︎

  2. interior mutability can also happen inside immutable variables ↩︎

  3. Which one should always group/decompose into the parts “&mut” and “T” – never into the parts “&” and “mut T”. ↩︎

4 Likes

Even an active exclusive reference and active, aliasing shared reference is UB. And you can create &T from a &&mut T via reborrowing. Aliasing UB aside, data races become trivial.

Somewhat related if you're into opsem adventures.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.