A variable declared with let is immutable by default — attempting to reassign it is a compile error, not a runtime warning. Writing let mut explicitly opts into mutability for that one variable. This default is deliberate: most variables in a well-structured program never need to change, and making mutation opt-in surfaces exactly which parts of the code do change state, which helps both the borrow checker and human readers reason about the program.
let vs. let mut
Reassigning a plain let variable is a compile error, caught before the program ever runs:
let x = 5;
// x = 6; // COMPILE ERROR: cannot assign twice to immutable variable
let mut y = 5;
y = 6; // fine — y was declared mutable
println!("{}", y); // 6Constants: always immutable, known at compile time
const declares a value that must be known at compile time and can never be mutable, even with mut — it's a distinct concept from an immutable let variable, which can still hold a value computed at runtime.
const MAX_RETRIES: u32 = 3; // must be a compile-time constant, type annotation requiredShadowing: a new variable, not a mutation
Declaring a new variable with let using a name that already exists doesn't mutate the old one — it creates a brand-new variable that happens to reuse the name, optionally with a different type. This is different from mut: the old value still can't be changed, and shadowing can even change the variable's type, which reassignment through mut could never do.
let spaces = " "; // spaces is a &str
let spaces = spaces.len(); // spaces is now a usize — a NEW variable, different type
println!("{}", spaces); // 3