Structs

Borrowing Struct Fields

Rust's borrow checker tracks borrows of struct fields at field granularity (often called split borrowing or disjoint field borrowing): you can borrow one field mutably (&mut s.a) while simultaneously borrowing another field immutably (&s.b) or mutably (&mut s.b), provided the fields are distinct.

Disjoint Field Borrowing

When you write &mut s.x and &s.y directly in the same scope, the compiler sees that .x and .y occupy non-overlapping byte offsets inside s and permits both borrows to coexist:

Split Borrowing of `state: GameState`:

  +------------------------------------------+
  | player_hp: i32   <--- &mut state.player_hp  (Exclusive mutable borrow of field 1)
  +------------------------------------------+
  | enemy_hp:  i32   <--- &mut state.enemy_hp   (Exclusive mutable borrow of field 2)
  +------------------------------------------+
  | log: Vec<String> <--- &state.log            (Shared read-only borrow of field 3)
  +------------------------------------------+
struct GameState {
    player_hp: i32,
    enemy_hp: i32,
    arena_name: String,
}

fn apply_turn(player: &mut i32, enemy: &mut i32, arena: &str) {
    *enemy -= 15;
    *player -= 5;
    println!("[{arena}] Player HP: {player}, Enemy HP: {enemy}");
}

fn main() {
    let mut state = GameState {
        player_hp: 100,
        enemy_hp: 80,
        arena_name: String::from("Citadel"),
    };

    // Borrow two fields mutably and a third field immutably at the exact same time:
    let p_ref = &mut state.player_hp;
    let e_ref = &mut state.enemy_hp;
    let name_ref = &state.arena_name;

    apply_turn(p_ref, e_ref, name_ref);
}
[Citadel] Player HP: 95, Enemy HP: 65

How Helper Methods Break Split Borrowing (E0502)

While the borrow checker understands disjoint fields within a function body (&mut self.x vs. &self.y), a method signature like fn get_x_mut(&mut self) -> &mut i32 tells the compiler that the entire self struct is mutably borrowed for the lifetime of the returned reference. Calling a second method on self while that reference is live fails with E0502:

// ❌ does not compile
struct Engine {
    rpm: u32,
    temperature: f32,
}

impl Engine {
    fn rpm_mut(&mut self) -> &mut u32 {
        &mut self.rpm
    }

    fn temp(&self) -> f32 {
        self.temperature
    }
}

fn main() {
    let mut engine = Engine {
        rpm: 3000,
        temperature: 90.0,
    };

    let rpm = engine.rpm_mut(); // Borrows ALL of `engine` mutably
    let current_temp = engine.temp(); // Error: cannot borrow `engine` as immutable
    *rpm += 500;
    println!("{rpm}, {current_temp}");
}
error[E0502]: cannot borrow `engine` as immutable because it is also borrowed as mutable
  --> src/main.rs:24:24
   |
23 |     let rpm = engine.rpm_mut(); // Borrows ALL of `engine` mutably
   |               ------ mutable borrow occurs here
24 |     let current_temp = engine.temp(); // Error: cannot borrow `engine` as immutable
   |                        ^^^^^^ immutable borrow occurs here
25 |     *rpm += 500;
   |     ----------- mutable borrow later used here

Patterns to Preserve Split Borrowing Across Methods

When callers need simultaneous access to multiple parts of a struct:

Technique How It Works
Access fields directly within the module Inside methods of Engine, write &mut self.rpm and self.temperature directly instead of calling getter methods on self.
Return a tuple of split borrows (split_mut) Provide a method fn parts_mut(&mut self) -> (&mut A, &mut B, &C) that performs the disjoint field borrow inside its body and returns all parts at once.
Decompose into sub-structs Group logically independent subsystems (AudioState, RenderState) into separate field structs so each subsystem can be passed to helper functions independently.
struct Engine {
    rpm: u32,
    temperature: f32,
}

impl Engine {
    // Returns disjoint field borrows simultaneously in a single method call:
    fn split_mut(&mut self) -> (&mut u32, &f32) {
        (&mut self.rpm, &self.temperature)
    }
}

fn main() {
    let mut engine = Engine {
        rpm: 3000,
        temperature: 90.0,
    };

    let (rpm, temp) = engine.split_mut();
    if *temp < 100.0 {
        *rpm += 500;
    }
    println!("Updated RPM: {}, Temp: {}", engine.rpm, engine.temperature);
}
Updated RPM: 3500, Temp: 90

Borrowing Fields Using ref and ref mut in Destructuring

When destructuring a struct by value or matching on it, you can borrow specific fields by reference using ref or ref mut (or by matching on &struct_val / &mut struct_val directly):

struct Account {
    id: u32,
    owner: String,
    balance_cents: i64,
}

fn main() {
    let mut acc = Account {
        id: 1,
        owner: String::from("Linus"),
        balance_cents: 50_00,
    };

    // Match on `&mut acc`: `id` is `&mut u32`, `owner` is `&mut String`, `balance_cents` is `&mut i64`
    let Account {
        id,
        owner,
        balance_cents,
    } = &mut acc;

    *balance_cents += 25_00;
    println!("Account #{id} ({owner}): {} cents", *balance_cents);
}
Account #1 (Linus): 7500 cents