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