Structs

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:

  1. 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 fields pub or exposing setter methods.
  2. Interior mutability for the inverse case: When a struct must be shared via an immutable reference &Struct while still updating an internal counter or cache, wrap that specific field in Cell<T>, RefCell<T>, Mutex<T>, or an atomic type (AtomicU64).