Variables And Mutability In Rust

Subscribe to my newsletter and never miss my upcoming articles

In the last article, we talked about how to use Cargo for Rust projects and highlighted some of its most common commands. We are now going to go further and start talking about variables and mutability.

One of Rust's main features is that variables are immutable by default, which means that values cannot change once set. This might seem odd at first, mainly because we sometimes need to change values in order to make our program work, but fear not, you can still make them mutable!

I will try to solve most of the questions you may have about this, let's dive right in!

Variables

By definition, a variable is a value that can change, if you've been taught programming before or learned by yourself, this might sound familiar.

Rust takes a different approach when it comes to variables, instead of assuming that values will change throughout the lifespan of the program, it assumes that they won't and lets you, the programmer, decide which values will change. This lets you write your code in a way that takes advantage of the safety guarantees Rust offers.

When a variable is immutable, once a value is bound to a name, you cannot change that value. Let's see an example:

fn main() {
    let x = 5;
    println!("The value is: {}", x);
    x = 6;
    println!("The value is: {}", x);
}

Save and run the program using cargo run.You should receive an error message:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: make this binding mutable: `mut x`
3 |     println!("The value of x is: {}", x);
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables`.

To learn more, run the command again with --verbose.

Look closely for error[E0384]: cannot assign twice to immutable variable 'x', this might've already given you a clue of what's going on, but I'll go through the error message and explain what's going on in detail.

What this error is basically telling us is that we tried to change an immutable variable and since this is not allowed the compiler will yell at us, it might appear like a simple thing to complain about, but the Rust compiler is very strict and for good reason, the error messages only mean your program isn’t safely doing what you want it to do yet.

The Rust compiler not only checks for errors, but it also tries to give you suggestions on how to solve them, check for lines like this:

help: make this binding mutable: 'mut x'

Also, look for error codes in your compiler output, it might give you a better clue of what's going on. For example, error[E0384] is something you can look up and get help with, try either using rustc --explain E0384 or find the error code in the Rust Compiler Error Index.

Okay! Let's fix our program, taking all of the previous suggestions into account, you might've realized by now that we are missing a keyword in our variable definition, mut, this keyword will make our variables mutable:

fn main() {
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}

Therefore, you are now allowed to reassign the value for our x variable.

Differences between variables and constants

By now, if you have used constants before you might've thought

"Immutable variables are basically constants then?"

Well, not exactly, first, you aren’t allowed to use mut with constants, they aren't just immutable by default, they are immutable forever.

You declare constants with the const keyword and you must always annotate the data type, like so:

const MAX_POINTS: u32 = 100_000;
// Rust’s naming convention for constants is to use all uppercase with underscores between words, and underscores can be inserted in numeric literals to improve readability

Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.

Shadowing

There is a concept in Rust that most of you might be unfamiliar with, it's called shadowing, it allows you to declare a new variable with the same name as a previous variable. This is useful in cases where you want to take a previous value of a variable and assign it to itself with some modification, here's an example:

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;

    println!("The value of x is: {}", x);
}

As you can see, this program first binds x to a value of 5. Then it shadows x by repeating let x =, taking the original value, and adding 1 so the value of x is then 6. Then, the third let statement also shadows x, multiplying the previous value by 2 to give x a final value of 12. When we run this program, it will output the following:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x is: 12

Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed. By using shadowing, we are also able to reuse the same name as the previous variable.

What's next?

In the next article, we'll talk about data types.

If you enjoyed this article, consider sharing it so more people can benefit from it! Also, feel free to @me on Twitter or send me a message.

Mark's photo

A tricky part about Rust's immutability is this:

fn f() {
    let list = Vec::new();
    g(list);
}

fn g(mut list: Vec<i32>) {
    list.push(1);
    list = Vec::new();
}

We're changing the list that was immutable.

Maybe this seems weird for several reasons: 1) wasn't the list an immutable object? 2) why doesn't that work with borrows? 3) why does Rust let me change immutable to mutable?

The first thing to remember is that, just like other languages such as Java, it's the reference that is immutable, not the object itself. Just, unlike Java, this also applies recursively to anything within the reference.

Another thing to remember is that, if you pass an object to another method, you're either passing sole ownership, or you're sending a copy.

If function f passes sole ownership of the list to g, then f cannot have any references anymore. Whatever g does, it won't affect f. So it's no problem if g makes it mutable.

And if f passes a copy, of course it won't care what g does with that; it still has it's own version.

Mark's photo

I think it's useful to highlight that this is a much stronger type of immutability than e.g. Java's final, or equivalents in many languages.

If in Java we make a list:

final var list = new ArrayList<Integer>();

we cannot reassign it:

list = new ArrayList<Integer>();

but we can change it just fine in other ways:

list.add(1);
list.add(2);
list.clear();

Rust's Vec is a quite similar datatype Java's ArrayList:

let list = Vec::new();

but we cannot change it at all:

list.push(1);
list.push(2);
list.clear();

gives:

error[E0596]: cannot borrow `list` as mutable, as it is not declared as mutable

We might say that Java (and most others) has shallow immutability, while Rust's is deep.

There are some caveats though, mostly RefCell, Mutex and RwLock.

Eduardo Flores's photo

Thanks for your comments, I will make an article about smart pointers in the future where I will talk more about mutability.