References to Struct Fields
Rust's borrow checker tracks borrows at field granularity (known as split borrowing or disjoint field borrowing). When you borrow one field of a struct (&mut s.x), the compiler knows that other fields of the same struct (s.y, s.z) live at non-overlapping memory offsets and allows you to borrow them simultaneously—even mutably!
Disjoint Field Borrowing (&mut s.a and &mut s.b Simultaneously)
While you cannot take two &mut references to the entire struct at once (&mut s and &mut s), you can take simultaneous &mut (or & and &mut) references to distinct fields of the same struct or tuple:
Struct `Player` in Memory:
+------------------------+------------------------+
| health: i32 | position: (i32, i32) |
+------------------------+------------------------+
^ ^
│ │
`r_hp: &mut player.health` `r_pos: &player.position`
(Non-overlapping memory regions -> ALLOWED simultaneously!)
struct Player {
name: String,
health: i32,
score: u32,
}
fn heal_while_logging(health: &mut i32, name: &str) {
*health += 25;
println!("Healed '{name}' to {health} HP");
}
fn main() {
let mut player = Player {
name: String::from("Ferris"),
health: 75,
score: 100,
};
// Borrow `player.health` mutably AND `player.name` immutably in the same call!
heal_while_logging(&mut player.health, &player.name);
// Hold two simultaneous `&mut` references to two different fields:
let hp_mut: &mut i32 = &mut player.health;
let score_mut: &mut u32 = &mut player.score;
*hp_mut += 10;
*score_mut += 50;
println!("Final: {} has {} HP and {} pts", player.name, player.health, player.score);
}
Healed 'Ferris' to 100 HP
Final: Ferris has 110 HP and 150 pts
Whole-Struct Methods (&mut self) vs Field-Level Borrowing
Split borrowing works automatically whenever the compiler can see the field paths (&mut player.health and &player.name) directly inside the same function.
However, remember that rustc checks method calls using only the method's signature (&self or &mut self), not its body! If you call a method player.update(&mut self) while holding &player.name, the method signature &mut self claims an exclusive borrow of every field of player, triggering E0502:
// ❌ does not compile
struct Document {
title: String,
content: String,
}
impl Document {
// Signature `&mut self` borrows the ENTIRE `Document`, even though body only touches `content`!
fn append_line(&mut self, line: &str) {
self.content.push_str(line);
}
}
fn main() {
let mut doc = Document {
title: String::from("Report"),
content: String::new(),
};
let title_ref: &str = &doc.title;
doc.append_line(title_ref); // Error: `doc.append_line` borrows all of `doc` as `&mut`!
}
error[E0502]: cannot borrow `doc` as mutable because it is also borrowed as immutable
--> src/main.rs:21:5
|
20 | let title_ref: &str = &doc.title;
| ---------- immutable borrow occurs here
21 | doc.append_line(title_ref);
| ^^^^^^^^^^^^^^^^---------^
| | |
| | immutable borrow later used by call
| mutable borrow occurs here
error: aborting due to 1 previous error
Fixing Whole-Struct Method Conflicts
When a method only needs to mutate one field while reading another field:
- Pass the specific field (
&mut doc.content) to an associated/free function, or - Factor the grouped fields into smaller sub-structs (
struct Headerandstruct Body) so you can calldoc.body.append_line(&doc.header.title)cleanly!