104-borrowing-mutable — Mutable Borrowing
Tutorial
The Problem
Mutable references are the mechanism for safe mutation in Rust: you can modify data through a &mut T reference, but only one &mut T can exist at a time, and no shared &T references can coexist with it. This is Rust's compile-time implementation of the mutual exclusion principle: writers exclude all other accessors.
This rule prevents entire classes of bugs: iterator invalidation (mutating a collection while iterating over it), data races in concurrent code, and aliased mutation bugs common in C.
🎯 Learning Outcomes
&mut T at a time&mut T to functions to modify data without transferring ownership&mut T and &T cannot coexist for the same valuesplit_at_mut to get two non-overlapping mutable slicesCode Example
#![allow(clippy::all)]
// 104: Mutable Borrowing — &mut T
// Exclusive writer: only ONE &mut at a time
fn increment(x: &mut i32) {
*x += 1;
}
fn push_doubled(v: &mut Vec<i32>, val: i32) {
v.push(val * 2);
}
fn swap_first_last(v: &mut [i32]) {
if v.len() >= 2 {
let last_idx = v.len() - 1;
v.swap(0, last_idx);
}
}
// This won't compile — demonstrates the rule:
// fn bad_example() {
// let mut v = vec![1, 2, 3];
// let r1 = &mut v;
// let r2 = &mut v; // ERROR: second mutable borrow
// r1.push(4);
// r2.push(5);
// }
// Also can't mix &mut and &:
// fn bad_example2() {
// let mut v = vec![1, 2, 3];
// let r1 = &v; // shared borrow
// let r2 = &mut v; // ERROR: can't borrow as mutable
// println!("{:?}", r1);
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment() {
let mut x = 0;
increment(&mut x);
increment(&mut x);
assert_eq!(x, 2);
}
#[test]
fn test_push_doubled() {
let mut v = vec![1, 2];
push_doubled(&mut v, 3);
assert_eq!(v, vec![1, 2, 6]);
}
#[test]
fn test_swap() {
let mut v = vec![1, 2, 3, 4, 5];
swap_first_last(&mut v);
assert_eq!(v, vec![5, 2, 3, 4, 1]);
}
}Key Differences
&mut T exclusivity means mutable borrows are inherently thread-safe (no data races); OCaml requires explicit synchronisation.fn f(v: &mut Vec<i32>) explicitly declares mutation intent; OCaml's type system does not track mutation in function types.OCaml Approach
OCaml has no borrow checker. Mutable values use ref or mutable record fields:
let increment x = x := !x + 1
let () =
let n = ref 42 in
increment n; (* n is still accessible *)
let alias = n in (* both n and alias point to same ref *)
increment alias; (* modifies through alias *)
Printf.printf "%d
" !n (* 44 — aliasing is silent *)
OCaml allows aliased mutation freely. The programmer must reason about aliasing manually. Rust's borrow checker makes aliasing provably absent at compile time.
Full Source
#![allow(clippy::all)]
// 104: Mutable Borrowing — &mut T
// Exclusive writer: only ONE &mut at a time
fn increment(x: &mut i32) {
*x += 1;
}
fn push_doubled(v: &mut Vec<i32>, val: i32) {
v.push(val * 2);
}
fn swap_first_last(v: &mut [i32]) {
if v.len() >= 2 {
let last_idx = v.len() - 1;
v.swap(0, last_idx);
}
}
// This won't compile — demonstrates the rule:
// fn bad_example() {
// let mut v = vec![1, 2, 3];
// let r1 = &mut v;
// let r2 = &mut v; // ERROR: second mutable borrow
// r1.push(4);
// r2.push(5);
// }
// Also can't mix &mut and &:
// fn bad_example2() {
// let mut v = vec![1, 2, 3];
// let r1 = &v; // shared borrow
// let r2 = &mut v; // ERROR: can't borrow as mutable
// println!("{:?}", r1);
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment() {
let mut x = 0;
increment(&mut x);
increment(&mut x);
assert_eq!(x, 2);
}
#[test]
fn test_push_doubled() {
let mut v = vec![1, 2];
push_doubled(&mut v, 3);
assert_eq!(v, vec![1, 2, 6]);
}
#[test]
fn test_swap() {
let mut v = vec![1, 2, 3, 4, 5];
swap_first_last(&mut v);
assert_eq!(v, vec![5, 2, 3, 4, 1]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment() {
let mut x = 0;
increment(&mut x);
increment(&mut x);
assert_eq!(x, 2);
}
#[test]
fn test_push_doubled() {
let mut v = vec![1, 2];
push_doubled(&mut v, 3);
assert_eq!(v, vec![1, 2, 6]);
}
#[test]
fn test_swap() {
let mut v = vec![1, 2, 3, 4, 5];
swap_first_last(&mut v);
assert_eq!(v, vec![5, 2, 3, 4, 1]);
}
}
Deep Comparison
Core Insight
Only one &mut T at a time — this prevents data races at compile time, a guarantee no other mainstream language offers
OCaml Approach
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
rotate_left(v: &mut Vec<i32>, n: usize) function that rotates the vector in place using only mutable references.merge_in_place(a: &mut Vec<i32>, b: &[i32]) that merges b into the sorted a without allocating a new vector.split_at_mut to implement parallel_transform(v: &mut [i32]) that modifies the left half and right half of a slice independently.