Mutable References or Mutable Variables? Decoding Rust's mut-mut-mut

Published on
25-09-2023
Author
Aisys
Category
Opinions
https://cdn.aisys.pro/stories/mutable-references-or-mutable-variables-decoding-rusts-mut-mut-mut.jpg

Intro

Even though Rust provides its Rust book, I struggled with understanding the mut keyword. I asked myself, "Am I the only one who has this problem?" A quick Google search confirmed that I was not alone.


As a result, I decided to write this article to provide a detailed explanation of the mut keyword. This article is intended for those who come from high-level languages such as Python or JavaScript.

The Variables

To create an unmuttable variable in Rust, simply write let x = 1337. It's straightforward. If you want to create a variable that can be mutated later, just add the mut keyword after let. Rust has a helpful convention that encourages clarity of intentions.


Adding the mut keyword informs others that this variable will be modified somewhere else in the code. Okay.


Let's visualize it. Two variables here, let mut x = 1337 and let y = 42.

First - name; second - type, third - value, fourth - mut flag.

First - name; second - type, third - value, fourth - mut flag.

The References

At the moment, everything is straightforward. However, things start to get a little curly when using mut references. Let's create some.

let mut x = 1337;
let y = 42;

let x_ref = &mut x;
let y_ref = &y;


I created two references (or "borrowed" in terms of Rust). One of them is a mutable reference, and the other is a read-only reference. Let's create a scheme for that again.

image


In the given scheme, I have 4 variables, 2 of which are references. Both reference variables are immutable and do not have the mut keyword after let, which means I cannot change what they point to. However, I can still change the value they reference.

*x_ref = 777;


If you write this, the Rust compiler will not complain, and the value of x (not the ref itself) will change to 777. However, there's a red square on the scheme indicating that x_ref lacks the mutability option. So, why can I change the value it refers to?


Let’s come back to the scheme for the let x_ref = &mut x.

image


The first white block contains the name: x_ref. The second one informs me about the type stored in that variable. In its complete form, without any implicit type annotations, I can write as follows:

let x_ref: &mut i32 = &mut x;


I can interpret this as: let's create an immutable variable named x_ref that will hold a mutable reference to i32 and initialize it immediately with the mutable reference to the i32 value in the x variable.


This means I can modify the value it points to, but I cannot alter the reference's value (or address). In other words, I can't write something like:

let x_ref: &mut i32 = &mut x;
let mut z = 0;

x_ref = &mut z; // Not allowed!


In terms of the schemes, I want to change the direction the arrow is pointing to in the code block above. However, even if the z variable is mutable, I can't change the arrow because the problem lies in the immutability of the x_ref itself.


To change the arrow direction, I need to modify the address stored in the x_ref variable. However, I cannot do this as the variable is immutable.

image


Let’s do it!

let mut x: i32 = 1337;
let mut x_ref: &mut i32 = &mut x; // I've added mut before x_ref
let mut z = 0;

x_ref = &mut z; // Allowed!


There are too many instances of mut here around x_ref, right? Let's describe them.


  1. let mut x_ref: I am creating a mutable variable named x_ref, which means I can change its value later.


  2. &mut i32: I am stating that the variable will contain mutable reference(s) to some value of type i32.


  3. &mut x: I am borrowing (getting a reference to) the variable x.


Then, I created a variable named z and assigned it the value of 0. Afterward, when I wrote x_ref = &mut z, I indicated that I understand x_ref to be a mutable variable that can only hold references to i32 values.


Since the type of z is i32, I am able to assign its address to the x_ref variable. To obtain the address of z, I used the &mut z syntax.


The scheme.

image

The Mental Trick

Take a look at = in the statement, it may look a little bit obvious, but…

let mut x_ref = &mut x;


… I see it as a divider (especially if you rotate it by 90 degrees) that splits the statement into two sub-statements: left and right. The left side provides information about the variable itself, while the right side tells us about the value.


When I use the * dereference operator to change the value...

*x_ref = 100;

... I do not change the value of the x_ref variable. Instead, I am changing the value that x_ref is referencing.

Unmutable References

I used mut frequently before. What if I omit some of them?

let i = 1;
let j = 2;

let mut k = &i;


Can I change the value of i here? Using the divider technique, it's quite simple to answer. I can change the value of k (I see mut on the left side), but the value (right side) is an immutable reference to i (there is no mut here).


Therefore…

let i = 1;
let j = 2;

let mut k = &i;

k = &j; // This is legal.
*k = 3; // This is not.


The scheme.

image

Conclusion

In this article, we've dissected the nuances of the mut keyword and references. Remember, there's a distinction between a mutable reference and a mutable variable holding a reference. Our trick?


Using the = sign as a mental divider to better understand assignments in Rust. This simple visualization can clear up many confusions.


Happy coding!

Discussion (20)

Not yet any reply