title: Mutability and Immutability

In Rust, variables are immutable by default. You have to explicitly opt in to mutability with mut. This makes it easy to reason about which values can change and which can’t.

Variables are immutable by default

let x = 5;
x = 6;  // ERROR — cannot assign twice to immutable variable

Add mut to allow mutation:

let mut x = 5;
x = 6;  // fine

Why immutable by default?

It forces you to be explicit about what changes. If you see let x = ... you know x will never change. If you see let mut x = ... you know to pay attention — this value will be modified somewhere.

This also helps the compiler and other developers quickly identify which parts of your code have side effects.

Constants

Constants are always immutable, must have an explicit type, and can be declared in any scope including global:

const MAX_POINTS: u32 = 100_000;

Unlike let, constants can’t use mut and must be set to a value that’s known at compile time.

Shadowing

You can declare a new variable with the same name as an existing one — this is called shadowing:

let x = 5;
let x = x + 1;  // new x shadows the old one
let x = x * 2;
 
println!("{x}");  // 12

Shadowing is different from mut — you’re creating a new variable each time. This means you can even change the type:

let spaces = "   ";       // &str
let spaces = spaces.len(); // usize — fine with shadowing

With mut this would be an error since you can’t change a variable’s type.

Mutable references

A mutable reference lets a function modify a value it borrows:

fn add_one(n: &mut i32) {
    *n += 1;
}
 
let mut x = 5;
add_one(&mut x);
println!("{x}");  // 6

The * dereferences the pointer to get at the value inside. See [[borrowing]] for the rules around mutable references.

Mutability and structs

Mutability applies to the whole struct — you can’t have individual immutable fields on a mutable struct or vice versa:

struct Point {
    x: i32,
    y: i32,
}
 
let mut p = Point { x: 1, y: 2 };
p.x = 10;  // fine — whole struct is mutable
p.y = 20;  // fine
 
let p2 = Point { x: 1, y: 2 };
p2.x = 10;  // ERROR — p2 is immutable

Interior mutability

Sometimes you need to mutate data even when you only have an immutable reference to it. Rust provides RefCell<T> and Cell<T> for this — they move the borrow checks from compile time to runtime. See [[pointers]] for details.

Immutability is not deep

Immutability in Rust is shallow — it applies to the binding itself, not to what it points to:

let v = vec![1, 2, 3];
// v.push(4);  // ERROR — v is immutable
 
let mut v = vec![1, 2, 3];
v.push(4);  // fine

For heap data like Vec or String, mut controls whether you can modify the contents. For a Box<T>, mut controls whether you can modify what’s inside the box.