Borrow different parts of a struct simultaneously
Split borrowing is an advanced Rust pattern that allows you to borrow different fields of a struct simultaneously, even when some borrows are mutable and others are immutable. This pattern leverages Rust's granular borrow checking at the field level.
When working with structs, you might need to access multiple fields simultaneously. Without split borrowing, you'd be limited by Rust's borrowing rules at the struct level.
struct Player {
name: String,
health: u32,
position: (f32, f32),
inventory: Vec<String>,
}
impl Player {
// ā This doesn't work - can't have multiple mutable borrows
fn update_bad(&mut self) {
// This borrows the entire self mutably
self.health -= 10;
// Error: can't borrow self.position mutably here
// self.update_position();
}
// ā
This works - split borrowing at field level
fn update_good(&mut self) {
let health = &mut self.health;
let position = &mut self.position;
let name = &self.name; // immutable borrow is fine
*health -= 10;
position.0 += 1.0;
println!("{} moved to {:?}", name, position);
}
}
// Example with functions
fn process_player_data(health: &mut u32, position: &mut (f32, f32), name: &str) {
*health -= 5;
position.0 += 2.0;
println!("{} is at {:?} with {} health", name, position, health);
}
fn main() {
let mut player = Player {
name: String::from("Hero"),
health: 100,
position: (0.0, 0.0),
inventory: vec![],
};
// Split borrowing in action
process_player_data(
&mut player.health,
&mut player.position,
&player.name
);
println!("Final health: {}", player.health);
}
Rust's borrow checker operates at the field level within a function. When you explicitly borrow individual fields:
ā Use split borrowing when:
ā Avoid split borrowing when:
// ā Bad: Unnecessary split borrowing
fn set_health_bad(player: &mut Player, new_health: u32) {
let health = &mut player.health;
*health = new_health;
}
// ā
Good: Direct access is clearer
fn set_health_good(player: &mut Player, new_health: u32) {
player.health = new_health;
}
// ā Bad: Trying to split borrow when there's logical coupling
struct BankAccount {
balance: f64,
transaction_log: Vec<String>,
}
// This is a code smell - these fields are logically related
fn process_transaction_bad(
balance: &mut f64,
log: &mut Vec<String>,
amount: f64
) {
*balance += amount;
log.push(format!("Added {}", amount));
}
// ā
Good: Keep logically coupled operations together
impl BankAccount {
fn deposit(&mut self, amount: f64) {
self.balance += amount;
self.transaction_log.push(format!("Deposited {}", amount));
}
}
struct GameState {
score: u32,
time_remaining: f32,
player_position: (f32, f32),
enemy_positions: Vec<(f32, f32)>,
}
impl GameState {
fn update(&mut self, delta_time: f32) {
// Split borrowing enables parallel-looking code
update_timer(&mut self.time_remaining, delta_time);
update_player(&mut self.player_position, &mut self.score);
update_enemies(&mut self.enemy_positions, &self.player_position);
}
}
fn update_timer(time: &mut f32, delta: f32) {
*time -= delta;
}
fn update_player(position: &mut (f32, f32), score: &mut u32) {
position.0 += 0.1;
*score += 1;
}
fn update_enemies(enemies: &mut Vec<(f32, f32)>, player_pos: &(f32, f32)) {
for enemy in enemies.iter_mut() {
// Move enemies toward player
if enemy.0 < player_pos.0 { enemy.0 += 0.05; }
if enemy.1 < player_pos.1 { enemy.1 += 0.05; }
}
}
Bevy's ECS system heavily relies on split borrowing to allow systems to access different components simultaneously.
View on GitHubTokio uses split borrowing for IO operations, allowing read and write halves of sockets to be used independently.
View on GitHubSplit borrowing has zero runtime cost - it's purely a compile-time feature. The borrow checker ensures safety without any runtime overhead.
// These compile to identical machine code
fn method1(player: &mut Player) {
player.health -= 10;
player.position.0 += 1.0;
}
fn method2(player: &mut Player) {
let health = &mut player.health;
let position = &mut player.position;
*health -= 10;
position.0 += 1.0;
}
Implement a Rectangle struct with width and height fields. Write a function that can simultaneously modify both dimensions and calculate the area.
Create a Character struct with stats (health, mana, stamina) and equipment (weapon, armor). Implement a function that uses different equipment while updating stats.
Implement a thread-safe data structure where different threads can access different fields simultaneously using Arc for each field instead of the whole struct.
Run this code in the official Rust Playground