Yesterday we saw that let gives you an immutable binding. But sometimes you genuinely need a variable to change — a counter, a running total, a buffer being filled. For that, you use let mut.

The syntax

let mut count = 0;
count += 1;
count += 1;
println!("{}", count);  // 2

Adding mut tells both the compiler and the reader: this value is intentionally going to change.

Why does the distinction matter?

It’s a communication tool. When you see let in Rust code, you immediately know: this value won’t change. When you see let mut, you know to pay closer attention — something is being mutated here.

In a large Java codebase, you often have to trace through code to figure out whether a variable is ever reassigned. In Rust, it’s always explicit and right in the declaration.

A practical example: building a string

let mut result = String::new();
result.push_str("Hello");
result.push_str(", Rust!");
println!("{}", result);  // Hello, Rust!

String::new() creates an empty, growable string. Because we’re modifying it with push_str, we need mut.

Mutability is not the same as reassignment

mut allows you to modify a value in place. Shadowing (from yesterday) lets you replace a binding entirely. They’re different:

let mut x = 5;
x = 10;          // ✅ mutation — same variable, new value

let y = 5;
let y = 10;      // ✅ shadowing — new variable, same name

In practice: use let by default. Reach for let mut only when you need it. The compiler will remind you if you forgot — it warns you about mut bindings that are never actually mutated.