ExamplesBy LevelBy TopicLearning Paths
104 Intermediate

104-borrowing-mutable — Mutable Borrowing

Functional Programming

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

  • • Understand the exclusive mutable borrow rule: only one &mut T at a time
  • • Pass &mut T to functions to modify data without transferring ownership
  • • Understand that &mut T and &T cannot coexist for the same value
  • • Use split_at_mut to get two non-overlapping mutable slices
  • • Recognise the borrow checker errors that enforce the exclusivity rule
  • Code 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

  • Exclusivity enforcement: Rust's borrow checker prevents aliased mutation at compile time; OCaml allows it silently.
  • Concurrent safety: Rust's &mut T exclusivity means mutable borrows are inherently thread-safe (no data races); OCaml requires explicit synchronisation.
  • split_at_mut: Rust needs a special function to prove two slices do not overlap; OCaml can take two slices from the same array without compiler complaints.
  • Function signatures: Rust's 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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

  • • See example.ml for implementation
  • Rust Approach

  • • See example.rs for implementation
  • Comparison Table

    FeatureOCamlRust
    Seeexample.mlexample.rs

    Exercises

  • Write a rotate_left(v: &mut Vec<i32>, n: usize) function that rotates the vector in place using only mutable references.
  • Implement merge_in_place(a: &mut Vec<i32>, b: &[i32]) that merges b into the sorted a without allocating a new vector.
  • Use split_at_mut to implement parallel_transform(v: &mut [i32]) that modifies the left half and right half of a slice independently.
  • Open Source Repos