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 variableAdd mut to allow mutation:
let mut x = 5;
x = 6; // fineWhy 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}"); // 12Shadowing 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 shadowingWith 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}"); // 6The * 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 immutableInterior 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); // fineFor 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.