[Rust] Variables and Mutability: Immutability, mut, Constants, Shadowing

These are my notes on The Rust Programming Language, chapter 3.1 "Variables and Mutability"; the examples are based on that chapter.

This post covers variables and mutability, constants, and shadowing.


Variables are Immutable by Default

In Rust, when you declare a variable using let, it is immutable by default. This means once you bind a value to a name, you can't change it.

If one part of your code assumes a value will stay the same but another part changes it, you can end up with a bug. Rust prevents this at compile time.

Take a look at this code that won't compile:

Rust
fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6; // Error! Cannot assign twice to immutable variable
    println!("The value of x is: {x}");
}

Compiling it fails with error[E0384]: cannot assign twice to immutable variable x``.


Making Variables Mutable with mut

Of course, sometimes you need to change a value. In those cases, you can opt-out of the default behavior by adding the mut keyword in front of the variable name.

Rust
fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6; // This works perfectly!
    println!("The value of x is: {x}");
}

Using mut also tells anyone reading your code that this variable is meant to change.


Constants

Variables (even immutable ones) are different from constants. Constants are declared using the const keyword and are always immutable.

Here are the rules for constants:

  1. You can't use mut with them.
  2. You must annotate the type (like u32 or f64).
  3. They can only be set to values that can be calculated at compile time.
Rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Constants are useful for values that never change while the program runs, like the speed of light or a maximum health limit in a game.


Shadowing

Rust supports shadowing. This is when you declare a new variable with the same name as a previous one. The second variable "shadows" the first one.

Rust
fn main() {
    let x = 5;
    let x = x + 1; // x is now 6

    {
        let x = x * 2; // x is now 12 inside this scope
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}"); // x is back to 6 here
}

Why use Shadowing instead of mut?

  • Change Types: You can change the data type while keeping the same name (e.g., converting a string of spaces " " to its length 3).
  • Keep Immutability: After the transformation is done, the variable remains immutable, keeping your code safe.

Summary Table

FeatureImmutable VariableMutable Variable (mut)Constant (const)
Changeable?NoYesNo
Type AnnotationOptionalOptionalRequired
Shadowing?YesYesNo
ScopeLocalLocalGlobal or Local