ExamplesBy LevelBy TopicLearning Paths
898 Intermediate

898-borrowing-mutable — Mutable Borrowing (&mut T)

Functional Programming

Tutorial

The Problem

In-place mutation is often more efficient than allocating new values. Sorting a large array in place, incrementing a counter, reversing a buffer — all benefit from direct mutation. Rust allows mutable access via &mut T with one strict rule: only one &mut T can exist at a time, and no &T can coexist with it. This prevents data races and iterator invalidation — the bugs that plagued C++ with concurrent mutation. The rule "exclusive access implies safe mutation" is Rust's solution to the aliasing problem that makes optimizing languages hard.

🎯 Learning Outcomes

  • • Use &mut T to mutate data through a reference without taking ownership
  • • Understand the "exactly one mutable reference" rule and why it prevents data races
  • • Perform in-place operations: reversal, doubling, and accumulation
  • • Recognize how iter_mut() provides mutable iteration over a collection
  • • Compare with OCaml's ref cells and Array for explicit mutable state
  • Code Example

    pub fn increment(c: &mut Counter) {
        c.count += 1;
    }
    
    pub fn sum_into(data: &[i32], total: &mut i32) {
        for &x in data {
            *total += x;
        }
    }
    
    pub fn reverse_in_place(arr: &mut [i32]) {
        let n = arr.len();
        for i in 0..n / 2 {
            arr.swap(i, n - 1 - i);
        }
    }

    Key Differences

  • Exclusivity: Rust enforces one &mut T at a time; OCaml allows multiple references to the same ref cell.
  • Data race prevention: Rust's single-writer rule prevents data races even in single-threaded code (for consistent reasoning); OCaml relies on the programmer.
  • Signature signals intent: Rust &mut T in a function signature explicitly declares "this function may modify the argument"; OCaml has no such declaration.
  • Slice mutation: Rust &mut [T] enables in-place algorithms on slices with zero overhead; OCaml arrays support the same via arr.(i) <- value.
  • OCaml Approach

    OCaml uses ref cells for mutable variables: let total = ref 0 in List.iter (fun x -> total := !total + x) xs. Arrays are mutable: Array.iteri (fun i x -> arr.(i) <- x * 2) arr. OCaml's Buffer.t is a mutable string builder. Unlike Rust, OCaml allows multiple references to the same mutable value — no "one writer" restriction. This means OCaml programs can have aliasing mutations that would be compile errors in Rust, potentially leading to harder-to-debug state bugs.

    Full Source

    #![allow(clippy::all)]
    // Example 104: Mutable References (&mut T)
    //
    // Only ONE &mut T at a time. No &T while &mut T exists.
    // This prevents data races at compile time.
    
    // Approach 1: Mutable reference to a struct
    pub struct Counter {
        pub count: i32,
    }
    
    pub fn increment(c: &mut Counter) {
        c.count += 1;
    }
    
    pub fn get_count(c: &Counter) -> i32 {
        c.count
    }
    
    // Approach 2: Mutable reference for accumulation
    // Takes a mutable reference to the total — the caller owns the value,
    // and we write into it without taking ownership.
    pub fn sum_into(data: &[i32], total: &mut i32) {
        for &x in data {
            *total += x;
        }
    }
    
    // Approach 3: In-place mutation via &mut [T]
    // Reverses a slice in place — no allocation, mutates through the reference.
    pub fn reverse_in_place(arr: &mut [i32]) {
        let n = arr.len();
        for i in 0..n / 2 {
            arr.swap(i, n - 1 - i);
        }
    }
    
    // Approach 4: Exclusive access — demonstrates borrow checker rules.
    // We can pass &mut to a helper and regain access after it returns.
    pub fn double_all(values: &mut [i32]) {
        for v in values.iter_mut() {
            *v *= 2;
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_counter_increment() {
            let mut c = Counter { count: 0 };
            increment(&mut c);
            increment(&mut c);
            increment(&mut c);
            assert_eq!(get_count(&c), 3);
        }
    
        #[test]
        fn test_counter_starts_at_zero() {
            let c = Counter { count: 0 };
            assert_eq!(get_count(&c), 0);
        }
    
        #[test]
        fn test_sum_into_accumulates() {
            let mut total = 0;
            sum_into(&[1, 2, 3, 4, 5], &mut total);
            assert_eq!(total, 15);
        }
    
        #[test]
        fn test_sum_into_empty_slice() {
            let mut total = 42;
            sum_into(&[], &mut total);
            assert_eq!(total, 42);
        }
    
        #[test]
        fn test_sum_into_accumulates_across_calls() {
            let mut total = 0;
            sum_into(&[1, 2, 3], &mut total);
            sum_into(&[4, 5], &mut total);
            assert_eq!(total, 15);
        }
    
        #[test]
        fn test_reverse_in_place_even() {
            let mut arr = [1, 2, 3, 4];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [4, 3, 2, 1]);
        }
    
        #[test]
        fn test_reverse_in_place_odd() {
            let mut arr = [1, 2, 3];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [3, 2, 1]);
        }
    
        #[test]
        fn test_reverse_in_place_single() {
            let mut arr = [42];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [42]);
        }
    
        #[test]
        fn test_reverse_in_place_empty() {
            let mut arr: [i32; 0] = [];
            reverse_in_place(&mut arr);
            assert_eq!(arr, []);
        }
    
        #[test]
        fn test_double_all() {
            let mut values = vec![1, 2, 3, 4];
            double_all(&mut values);
            assert_eq!(values, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_double_all_empty() {
            let mut values: Vec<i32> = vec![];
            double_all(&mut values);
            assert_eq!(values, vec![]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_counter_increment() {
            let mut c = Counter { count: 0 };
            increment(&mut c);
            increment(&mut c);
            increment(&mut c);
            assert_eq!(get_count(&c), 3);
        }
    
        #[test]
        fn test_counter_starts_at_zero() {
            let c = Counter { count: 0 };
            assert_eq!(get_count(&c), 0);
        }
    
        #[test]
        fn test_sum_into_accumulates() {
            let mut total = 0;
            sum_into(&[1, 2, 3, 4, 5], &mut total);
            assert_eq!(total, 15);
        }
    
        #[test]
        fn test_sum_into_empty_slice() {
            let mut total = 42;
            sum_into(&[], &mut total);
            assert_eq!(total, 42);
        }
    
        #[test]
        fn test_sum_into_accumulates_across_calls() {
            let mut total = 0;
            sum_into(&[1, 2, 3], &mut total);
            sum_into(&[4, 5], &mut total);
            assert_eq!(total, 15);
        }
    
        #[test]
        fn test_reverse_in_place_even() {
            let mut arr = [1, 2, 3, 4];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [4, 3, 2, 1]);
        }
    
        #[test]
        fn test_reverse_in_place_odd() {
            let mut arr = [1, 2, 3];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [3, 2, 1]);
        }
    
        #[test]
        fn test_reverse_in_place_single() {
            let mut arr = [42];
            reverse_in_place(&mut arr);
            assert_eq!(arr, [42]);
        }
    
        #[test]
        fn test_reverse_in_place_empty() {
            let mut arr: [i32; 0] = [];
            reverse_in_place(&mut arr);
            assert_eq!(arr, []);
        }
    
        #[test]
        fn test_double_all() {
            let mut values = vec![1, 2, 3, 4];
            double_all(&mut values);
            assert_eq!(values, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_double_all_empty() {
            let mut values: Vec<i32> = vec![];
            double_all(&mut values);
            assert_eq!(values, vec![]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Mutable References (&mut T)

    Side-by-Side Code

    OCaml

    (* Mutable record fields — mutation is opt-in per field *)
    type counter = { mutable count : int }
    
    let increment c = c.count <- c.count + 1
    
    (* Ref cells — first-class mutable references *)
    let sum_into total lst =
      List.iter (fun x -> total := !total + x) lst
    
    (* Mutable arrays — in-place mutation *)
    let reverse_in_place arr =
      let n = Array.length arr in
      for i = 0 to n / 2 - 1 do
        let tmp = arr.(i) in
        arr.(i) <- arr.(n - 1 - i);
        arr.(n - 1 - i) <- tmp
      done
    

    Rust (idiomatic)

    pub fn increment(c: &mut Counter) {
        c.count += 1;
    }
    
    pub fn sum_into(data: &[i32], total: &mut i32) {
        for &x in data {
            *total += x;
        }
    }
    
    pub fn reverse_in_place(arr: &mut [i32]) {
        let n = arr.len();
        for i in 0..n / 2 {
            arr.swap(i, n - 1 - i);
        }
    }
    

    Rust (iterator style)

    pub fn double_all(values: &mut Vec<i32>) {
        for v in values.iter_mut() {
            *v *= 2;
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    Mutable struct fieldtype t = { mutable x : int }struct T { x: i32 } + &mut T
    Ref cellint ref (ref 0, !r, :=)&mut i32 (explicit, checked)
    Mutable sliceint array (always mutable)&mut [i32] (exclusive borrow)
    Mutation operator<- (fields), := (refs)*r = ... or field = ...

    Key Insights

  • Exclusivity is enforced at compile time. Rust's borrow checker statically guarantees that &mut T is unique — no two mutable aliases can coexist. OCaml's ref cells and mutable fields allow aliasing freely; the programmer bears responsibility for avoiding races.
  • OCaml mutation is opt-in per field; Rust mutation is opt-in per binding. In OCaml you mark individual record fields mutable. In Rust you declare a binding let mut x and pass &mut x — the mutability travels with the reference, not the type declaration.
  • **Rust &mut replaces OCaml ref without allocation.** An OCaml ref is a heap-allocated box. Rust &mut i32 is a stack reference — zero overhead. The borrow checker makes this safe where OCaml needs garbage collection to manage ref-cell lifetimes.
  • **iter_mut() is the Rust analogue of List.iter with mutation.** OCaml's List.iter (fun x -> total := !total + x) reads x but writes through the captured ref. Rust's iter_mut() hands out &mut to each element directly, keeping all mutation explicit and checked.
  • No data races by construction. While &mut T exists, the borrow checker forbids any shared &T references. This rule is the compile-time equivalent of a mutex — enforced without runtime cost and impossible to forget.
  • When to Use Each Style

    **Use &mut T (Rust) when:** you need to mutate a value in place without transferring ownership — counters, accumulators, in-place sorting, filling buffers. The exclusivity guarantee means you never need a lock for single-threaded code.

    **Use ref / mutable fields (OCaml) when:** you need shared mutable state across closures or callbacks within a single-threaded context. OCaml's GC manages lifetimes so aliasing is safe (single-threaded), but you lose the static race-freedom guarantee Rust provides.

    Exercises

  • Implement normalize_in_place(data: &mut [f64]) that divides each element by the maximum value, mutating in place.
  • Write fill_with<T: Clone>(data: &mut [T], value: T) that overwrites all elements with the given value.
  • Implement a Queue<T> backed by Vec<T> with enqueue(&mut self, item: T) and dequeue(&mut self) -> Option<T> methods.
  • Open Source Repos