References and Borrowing

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:

  1. Pass the specific field (&mut doc.content) to an associated/free function, or
  2. Factor the grouped fields into smaller sub-structs (struct Header and struct Body) so you can call doc.body.append_line(&doc.header.title) cleanly!