Mutability
Learn how to make variables mutable in Rust using the mut keyword, when to use it, and how it changes the compiler's guarantees.
Using mut to Make Variables Mutable
In Rust, declaring a variable with let binds a name to a value, but that binding is immutable by default. You cannot change the value once it is set. If you try, the compiler refuses to build the program.
That is where the mut keyword comes in. Adding mut before the variable name tells the compiler: this binding is allowed to be reassigned.
fn main() {
let mut x = 5;
println!("x is {x}");
x = 10;
println!("x is now {x}");
}
When you run this, the output confirms the reassignment happened:
x is 5
x is now 10
The mut keyword is not a property of the value 5 — it is a permission attached to the variable binding x. The binding is what the compiler tracks; the value itself does not "know" whether the variable holding it is mutable. This distinction matters once you start working with references and data structures.
If you leave out mut, you see the compiler's enforcement mechanism directly. The following code:
fn main() {
let x = 5;
x = 10; // error!
}
produces a diagnostic like:
error[E0384]: cannot assign twice to immutable variable `x`
The error message says exactly what happened: you attempted to assign to a variable you never marked as mutable. The fix is either to add mut or to reconsider whether the value truly needs to change.
Compile error from missing mut:
Forgetting mut is one of the most common early mistakes when learning Rust. The compiler error will always point you to the line that attempted the assignment. Add mut to the declaration and recompile — the fix is mechanical.
Why Rust Requires Explicit Mutability
Many languages let you change any variable unless you explicitly lock it down with something like const or final. Rust inverts that choice: immutability is the default, and you must deliberately opt into mutability with mut.
This design reduces accidental state changes. A variable that is not marked mut cannot be altered, no matter how far the code drifts from the original declaration. When you read a block of Rust code and see let x = ..., you know immediately that x will hold the same value for the rest of that scope. That guarantee eliminates whole categories of bugs — particularly bugs caused by unexpected side effects in large functions.
For a beginner, it helps to think of mut as a permission slip you hand to the compiler. Without it, the compiler treats the binding like a read-only label. With it, you are allowed to tear off the label and stick it on a different value later. This forces you to be intentional about which parts of your program require mutable state.
Immutable does not mean constant:
An immutable variable declared with let can still be initialized with a runtime value, such as the result of a function call. Rust has a separate concept of constants (const) that must be evaluated at compile time. Variables and constants serve different roles, even though they both forbid reassignment.
When to Use mut
Use mut when a variable genuinely needs to change during its lifetime. Common situations include:
- Loop counters or accumulators.
- State machines where the current state updates each iteration.
- Building up a value step by step, such as accumulating a sum or constructing a collection.
- Reassigning a variable after an operation that produces a new value, where creating a new binding would be unnatural.
Do not reach for mut out of habit. If a variable’s value never changes, leaving it immutable gives the compiler more freedom to optimize and gives readers less to keep in their heads.
Don't overuse mut:
Marking everything as mut “just in case” defeats Rust’s safety intent. You should only use mut when you can point to the line where the reassignment actually happens. If you find yourself adding mut without a clear reason, stop and reconsider.
Rust does not penalize you for changing your mind. Starting with an immutable binding and later adding mut when you realize a reassignment is necessary is a normal part of writing Rust.
Mutability and the Compiler’s Checks
When you declare let mut x = 5, the compiler grants certain capabilities to the binding x. You can reassign it directly, as shown earlier. More importantly, you can create a mutable reference to x with &mut x, which allows other parts of the program to modify the value through that reference.
fn main() {
let mut count = 0;
let r = &mut count;
*r += 1;
println!("{count}"); // 1
}
This works because count is mutable. If count were immutable, &mut count would be rejected at compile time.
The rule is straightforward: you can take a mutable reference only from a mutable variable. That rule feeds into Rust’s entire borrowing system, which you will explore in depth later. For now, treat mut as the gateway to any kind of in-place modification — whether direct assignment or through a reference.
Correct mutable reference:
If you can compile and run the example above and see 1 printed, you have correctly set up a mutable variable and used a mutable reference to modify it. This pattern is the foundation of safe mutation in Rust.
Common Misconceptions
“Shadowing is the same as mutability.”
Shadowing creates a new variable that happens to share the same name. The original binding stops existing in that scope. With mut, there is only one binding whose value changes. Shadowing and mut are different mechanisms and interact with the type system differently. For example, shadowing allows you to change the type of a variable, while mut does not.
// This compiles: shadowing changes the type
let value = "42";
let value = value.len();
// This does not compile: mut requires the same type
let mut value = "42";
value = value.len(); // error: expected &str, found usize
“mut makes the value mutable.”
It makes the binding mutable. For primitive types like integers, the distinction is subtle because reassigning the binding replaces the entire value. For composite types, the difference becomes tangible. For instance, a String stored in a mutable variable can be reassigned to a different String, but you still cannot modify the characters of a &str that lives behind an immutable reference — even if the variable holding the reference is mutable.
“I need mut for every variable that changes.”
Some structures provide interior mutability through types like Cell or RefCell, which allow mutation through an immutable reference. Those are advanced tools. In everyday Rust, mut is indeed the primary way to signal mutation, but it is not the only path.
Real-World Usage
In production Rust code, mut appears most often in function bodies, not in function signatures. A function might take an immutable argument and then create a local mutable copy for processing.
fn compute_score(entries: &[i32]) -> i32 {
let mut total = 0;
for &value in entries {
total += value;
}
total
}
Here total must be mutable because it accumulates the sum. The input slice remains immutable; the caller’s data is untouched. This pattern — taking immutable inputs and using local mutable state internally — is extremely common and keeps mutation contained.
Another frequent use is updating a variable inside a loop or while let block as a state machine advances. Rather than creating a new binding on each iteration, mut keeps the code concise.
Mut and performance:
There is no runtime cost to adding mut; the compiler emits the same machine code whether a variable is immutable or mutable. The keyword is purely a compile-time permission. Do not avoid mut out of fear of performance penalties.
Summary
Mutability in Rust is opt-in, not opt-out. The mut keyword is the single gateway to reassigning a variable or creating a mutable reference. Using it deliberately helps the compiler protect you from accidental changes and makes your intent explicit to anyone reading the code.
The key takeaway is not that immutability is always better — it is that you should always know which variables can change. mut is the tool that makes that knowledge visible.