mutKeyword
Explanation
mut marks a binding, reference, or raw pointer as mutable — the one
thing in Rust that is not mutable by default. It appears in a few
distinct syntactic positions that are easy to conflate:
- On a
letbinding:let mut x = 5;allowsxitself to be reassigned or mutated later. Withoutmut,x = 6;is a compile error. - On a reference type:
&mut Tis a mutable (exclusive) reference — a different type from&T, not a modifier of it. Only one&mut Tto a given value can exist at a time, and it cannot coexist with any&T. - On a function parameter pattern:
fn f(mut x: i32)makes the parameter binding mutable inside the function body — this is purely local; it says nothing about the caller's variable and has no effect on the function's signature/type. - On
self:&mut selfin a method signature borrows the receiver mutably.
mut is not part of a type in the let mut x sense (the binding is
mutable, not the type i32), but it is part of the type in the
reference sense (&mut T and &T are different types entirely).
Usage examples
Making a binding mutable with `let mut`
let mut x = 5; // <- `mut` allows `x` to be reassigned
x = 6;
Restriction: mut must appear at the binding site (let mut x); it
cannot be added later to make an already-immutable binding mutable.
Sharing state across threads
mut disappears from the signature when state is shared across threads —
the mutability moves into the lock, and the Arc binding itself stays
immutable even though what it points to is mutated from multiple threads.
use std::sync::{Arc, Mutex};
use std::thread;
let readings = Arc::new(Mutex::new(Vec::new())); // note: no `mut` — the Mutex supplies the mutability
let mut handles = Vec::new(); // <- `mut` needed here: the Vec of handles is grown with `push`
for sensor_id in 0..4 {
let readings = Arc::clone(&readings);
handles.push(thread::spawn(move || {
let mut batch = readings.lock().unwrap(); // <- `mut` binds the guard for write access
batch.push(sensor_id as f64 * 1.5);
}));
}
for handle in handles {
handle.join().unwrap();
}
Declaring the Arc binding mut would be misleading —
interior mutability means the binding is never reassigned; Clippy flags
unneeded mut bindings via
unused_mut.
Modifying an existing object
An object with invariants to protect exposes a &mut self method rather
than a public field a caller could set to any value directly.
struct Order { total_cents: u64, shipped: bool }
impl Order {
fn mark_shipped(&mut self) {
// <- `&mut self` lets the method mutate the receiver in place
self.shipped = true;
}
}
let mut order = Order { total_cents: 4200, shipped: false }; // <- `mut` needed: `order` is mutated below
order.mark_shipped();
Routing mutation through a method rather than a public
field keeps the invariant ("shipped only goes from false to true") in one
place — the
Book's method-syntax chapter
covers &mut self as the standard way to expose in-place mutation.
Mutating through a reference
Doubling every price in a slice in place needs a mutable reference to each element, not just a mutable binding to the slice itself.
fn double_all(values: &mut [f64]) {
for value in values.iter_mut() {
*value *= 2.0; // <- `mut` reference lets us write through `value`, not just read it
}
}
let mut prices = [9.99, 14.50, 3.25]; // <- `mut` required: `double_all` needs a mutable borrow
double_all(&mut prices);
iter_mut
is the standard way to mutate every element of a slice in place without
manual indexing, and the borrow checker guarantees only one &mut
exists at a time, ruling out aliasing bugs at compile time.