Mutable Structs
In Rust, mutability is a property of the variable binding or reference (let mut s or &mut Struct), never of individual struct fields. You cannot mark a single field as mut inside a struct definition; either the entire struct binding is mutable, or the entire struct binding is immutable.
Binding-Level Mutability Rules
| Binding / Reference Form | Can Read Fields? | Can Mutate Fields (s.f = val)? |
Can Take &mut s.f? |
|---|---|---|---|
let s = MyStruct { .. }; |
Yes | No | No |
let mut s = MyStruct { .. }; |
Yes | Yes (all accessible fields) | Yes |
s: &MyStruct |
Yes | No | No |
s: &mut MyStruct |
Yes | Yes (all accessible fields) | Yes |
// ❌ does not compile
struct InvalidPoint {
mut x: i32, // Rust does not allow `mut` on struct field declarations
y: i32,
}
fn main() {}
error: expected identifier, found keyword `mut`
--> src/main.rs:2:5
|
1 | struct InvalidPoint {
| ------------ while parsing this struct
2 | mut x: i32, // Rust does not allow `mut` on struct field declarations
| ^^^ expected identifier, found keyword
Mutating Fields via let mut and &mut
When a struct instance is bound with let mut (or passed as &mut Struct), every accessible field of the struct can be modified in place:
#[derive(Debug)]
struct Counter {
label: String,
count: u64,
}
fn increment_by(counter: &mut Counter, delta: u64) {
counter.count += delta;
}
fn main() {
let mut hits = Counter {
label: String::from("GET /health"),
count: 0,
};
hits.count += 1;
increment_by(&mut hits, 4);
hits.label.push_str(" (200 OK)");
println!("{hits:?}");
}
Counter { label: "GET /health (200 OK)", count: 5 }
Freezing and Unfreezing a Struct Across Moves
Because mutability belongs to the variable binding rather than the value itself, moving an immutable struct into a let mut binding makes it mutable, and moving a mutable struct into a plain let binding freezes it as immutable:
#[derive(Debug)]
struct Config {
threads: usize,
verbose: bool,
}
fn main() {
// 1. Build and mutate using a mutable binding:
let mut draft = Config {
threads: 4,
verbose: false,
};
draft.threads = 8;
draft.verbose = true;
// 2. Move `draft` into an immutable binding to freeze it for the rest of `main`:
let config: Config = draft;
println!("Frozen config: {config:?}");
}
Frozen config: Config { threads: 8, verbose: true }
How to Restrict Field Mutation When a Binding Is mut
If you want callers with a let mut binding to be able to mutate some fields of a struct while keeping other fields (such as an id or invariant) read-only:
- Module-level encapsulation (zero-cost, compile-time): Keep the read-only field private (
id: u64) and provide only a shared getter method (pub fn id(&self) -> u64), while making mutable fieldspubor exposing setter methods. - Interior mutability for the inverse case: When a struct must be shared via an immutable reference
&Structwhile still updating an internal counter or cache, wrap that specific field inCell<T>,RefCell<T>,Mutex<T>, or an atomic type (AtomicU64).