The mut keyword in Rust
Rust implements explicit mutability declaration. However, rust has a very unique and precise definition of what “mutating” a variable means, and this might trip up new users.
Also, rust’s borrow rules makes declaring variables as “constant” or “mutable” much more meaningful than in other programming languages.
I’ll explain here what mut
really means in rust.
The tl;dr is: declaring a variable as mut
just allows you to create a mutable reference to it, and assign new values to it nothing more, nothing less.
Let’s start with some code:
use std::collections::HashMap;
let my_map: HashMap<usize, String> = HashMap::new();
Here, we create a new hash map, call it my_map
. We own my_map
, because what my_map
is, is not a reference (&Foo
) or mutable reference (&mut Foo
).
But, when you do:
let item = my_map.get(103434);
You are creating a reference to my_map
. The get
method on HashMap
accepts a &self
, a reference. But my_map
is not a reference, this should be a compilation error! Mismatching types. What is actually happening is that the compiler inserts a reference here.
let item = (&my_map).get(103434);
The same is true for the insert
method. insert
accepts a &mut self
, a mutable reference. So, behind the scenes, rust creates a mutable reference to my_map
when you do:
let my_map: HashMap<usize, String> = HashMap::new();
// !!!!! ERROR, does not compile !!!!
my_map.insert(340101, "My String".to_owned());
// (&mut my_map).insert(...)
In fact, the above snippet of code doesn’t compile. This is because rust requires all variables for which you create mutable references to be declared with the mut
keyword.
let mut my_map: HashMap<usize, String> = HashMap::new();
// ^^^
// 👌 💯
my_map.insert(340101, "My String".to_owned());
In rust, function parameters act like variable declarations, so they follow the same rule.
Why can I mutate a mutable reference without mut
?
Ok, but why can I do this?
let mut my_map: HashMap<usize, String> = HashMap::new();
let my_other_map = &mut my_map;
my_other_map.insert(340101, "My String".to_owned());
See? No mut my_other_map
, only let
!
Indeed, the only things you need to declare with mut
are:
- Things you create a mutable reference of.
- Things you assign to, as in
foo = new_foo;
my_other_map
is already a mutable reference, and you are not assigning to it. So it doesn’t need to be declared mut
.
Besides, the &mut
here in the declaration is a dead giveaway that you are going to mutate my_other_map
.
By all means, you still can create a mutable reference to your mutable reference if you really want to.
let mut my_map: HashMap<usize, String> = HashMap::new();
let my_other_map = &mut my_map;
// !!!!! ERROR, does not compile !!!!!
// need to declare mut my_other_map
let my_sneaky_map: &mut &mut HashMap<_, _> = &mut my_other_map;
In comparison to other languages
Rust follows a recent trend in languages design, which is to make the declaration of mutable variable distinct from immutable variables.
However, in other programming languages, everything has interior mutability. Interior mutability is when you modify the inside of a class without modifying the outside. In any project of meaningful size, you more often change the inside of classes than their complete value (in fact, it’s very rare to change the value of something without going through a method). All implementations of explicit mutability declaration I have seen, do not, in any way, prevent interior mutability. Even rust.
As a result, the only way to ensure immutability in those languages, is by only exposing non-mutating methods to your class and marking all fields private.
In scala, you can use val
and var
to declare a variable as “immutable” vs “mutable”, but this is definitively misleading. You can very much declare a java hash map as val
and then insert new items in it. However, since the scala standard library is immutable, there is no point in worrying about mutability or interior mutability. (unless of course, you are using java classes or your scala classes themselves contain java classes) The compiler doesn’t in anyway guarantee that your val
won’t be mutated or won’t have a mutable method. So what is var
for? It’s a sort of inline documentation (with all the pitfalls that this entails). And honestly, I’m not too hot on it.
This is also true of javascript’s var
vs const
, and C’s const
(beyond the complete nonsense related to declaration priority and what const
is actually const
-ing between the type, the reference or god knows what)
const we_have_consts = { a: 10 }
// Wait, this doesn't even cause a runtime error???
we_have_consts.a = 34
console.log(we_have_consts.a)
// 34
Rust also has interior mutability, and indeed, it can trip up users to see a method taking a &self
and modifying the value of self
.
However, this is far from usual, and it’s mostly constrained to specialized concurrent libraries such as dashmap.
(also, &mut T
itself has interior mutability, by definition, you are mutating something “pointed to” by the reference, aka “inside” of it)
In general, the borrowing rules prevent all spooky action at a distance, at least, as long as the unsafe
code you are using is sound.