ExamplesBy LevelBy TopicLearning Paths
546 Intermediate

Reborrowing Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Reborrowing Patterns" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Reborrowing is the implicit mechanism by which Rust creates a shorter-lived reference from a longer-lived one. Key difference from OCaml: 1. **Implicit mechanism**: Rust reborrowing happens automatically when needed — most users never think about it explicitly; OCaml has no equivalent because mutation and reading are always available.

Tutorial

The Problem

Reborrowing is the implicit mechanism by which Rust creates a shorter-lived reference from a longer-lived one. When you pass &mut x to a function that takes &mut i32, Rust does not move the mutable reference — it creates a reborrow that lasts only for the function call. When the call returns, the original &mut x is available again. Without reborrowing, using &mut references would require move semantics — you could only use a mutable reference once. Understanding reborrowing explains why &mut chains work intuitively in practice.

🎯 Learning Outcomes

  • • What reborrowing is: creating a shorter-lived reference from a longer-lived one
  • • How &mut T implicitly reborrows as &T when passed to a function expecting &T
  • • How explicit reborrow &*r creates a shared reference from a mutable one
  • • How method chains on &mut self work through repeated reborrowing
  • • Why reborrowing enables &mut references to be used multiple times in sequence
  • Code Example

    // Implicit reborrow: &mut T -> &T
    pub fn demo() {
        let mut x = 42;
        let r = &mut x;
    
        let val = read_value(r);  // reborrows as &i32
        *r += 1;                   // original borrow still valid
    }
    
    // Explicit reborrow: &*r
    let shared: &i32 = &*r;

    Key Differences

  • Implicit mechanism: Rust reborrowing happens automatically when needed — most users never think about it explicitly; OCaml has no equivalent because mutation and reading are always available.
  • Reference reuse: Rust &mut references can be used multiple times via reborrowing; without reborrowing, they would need to be moved on every use (like Box).
  • Lifetime of reborrow: The reborrow's lifetime is strictly shorter than the original — Rust enforces this statically; OCaml references have no lifetime hierarchy.
  • Method chains: Rust method chains on &mut self work through a sequence of reborrows at each method call boundary; OCaml method calls on mutable values work without restriction.
  • OCaml Approach

    OCaml's ref values are always accessible and never "consumed" — there is no reborrow concept because mutation and reading are always available through the same reference:

    let x = ref 42
    let _ = !x            (* read *)
    let () = incr x       (* mutate *)
    let _ = !x            (* read again — no reborrow needed *)
    

    Full Source

    #![allow(clippy::all)]
    //! Reborrowing Patterns
    //!
    //! Creating sub-borrows from existing borrows.
    
    /// Read a value through a reference.
    pub fn read_value(r: &i32) -> i32 {
        *r
    }
    
    /// Increment through a mutable reference.
    pub fn increment(r: &mut i32) {
        *r += 1;
    }
    
    /// Demonstrate implicit reborrow: &mut T -> &T.
    pub fn implicit_reborrow_demo() -> i32 {
        let mut x = 42;
        let r = &mut x;
    
        // &mut T coerces to &T (implicit reborrow)
        let val = read_value(r); // r reborrowed as &i32
                                 // r still valid — reborrow ended
    
        *r += 1; // can still use r
        val
    }
    
    /// Explicit reborrow with &*.
    pub fn explicit_reborrow(r: &mut i32) -> i32 {
        let shared: &i32 = &*r; // explicit reborrow
        *shared
    }
    
    /// Reborrow in method chains.
    pub struct Counter {
        value: i32,
    }
    
    impl Counter {
        pub fn new(value: i32) -> Self {
            Counter { value }
        }
    
        pub fn get(&self) -> i32 {
            self.value
        }
    
        pub fn increment(&mut self) -> &mut Self {
            self.value += 1;
            self // return &mut self for chaining
        }
    
        pub fn double(&mut self) -> &mut Self {
            self.value *= 2;
            self
        }
    }
    
    /// Reborrow through function parameter.
    pub fn process_twice(r: &mut i32) {
        increment(r); // implicit reborrow
        increment(r); // another reborrow
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_implicit_reborrow() {
            let result = implicit_reborrow_demo();
            assert_eq!(result, 42);
        }
    
        #[test]
        fn test_explicit_reborrow() {
            let mut x = 10;
            let result = explicit_reborrow(&mut x);
            assert_eq!(result, 10);
        }
    
        #[test]
        fn test_counter_chain() {
            let mut counter = Counter::new(1);
            counter.increment().double().increment();
            assert_eq!(counter.get(), 5); // (1+1)*2+1 = 5
        }
    
        #[test]
        fn test_process_twice() {
            let mut x = 0;
            process_twice(&mut x);
            assert_eq!(x, 2);
        }
    
        #[test]
        fn test_reborrow_pattern() {
            let mut v = vec![1, 2, 3];
            let r = &mut v;
    
            // Each push reborrows r
            r.push(4);
            r.push(5);
    
            assert_eq!(*r, vec![1, 2, 3, 4, 5]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_implicit_reborrow() {
            let result = implicit_reborrow_demo();
            assert_eq!(result, 42);
        }
    
        #[test]
        fn test_explicit_reborrow() {
            let mut x = 10;
            let result = explicit_reborrow(&mut x);
            assert_eq!(result, 10);
        }
    
        #[test]
        fn test_counter_chain() {
            let mut counter = Counter::new(1);
            counter.increment().double().increment();
            assert_eq!(counter.get(), 5); // (1+1)*2+1 = 5
        }
    
        #[test]
        fn test_process_twice() {
            let mut x = 0;
            process_twice(&mut x);
            assert_eq!(x, 2);
        }
    
        #[test]
        fn test_reborrow_pattern() {
            let mut v = vec![1, 2, 3];
            let r = &mut v;
    
            // Each push reborrows r
            r.push(4);
            r.push(5);
    
            assert_eq!(*r, vec![1, 2, 3, 4, 5]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Reborrowing

    OCaml

    (* No concept of reborrowing — refs work differently *)
    let x = ref 42
    let read_value r = !r
    let increment r = r := !r + 1
    
    let () =
      let _ = read_value x in
      increment x
    

    Rust

    // Implicit reborrow: &mut T -> &T
    pub fn demo() {
        let mut x = 42;
        let r = &mut x;
    
        let val = read_value(r);  // reborrows as &i32
        *r += 1;                   // original borrow still valid
    }
    
    // Explicit reborrow: &*r
    let shared: &i32 = &*r;
    

    Key Differences

  • OCaml: ref cells with deref/assign operators
  • Rust: Implicit reborrowing for ergonomics
  • Rust: &mut T automatically reborrows as &T
  • Rust: Enables method chaining with &mut self
  • Both: Allow temporary shared access to mutable data
  • Exercises

  • Sequential reborrows: Write a function that takes r: &mut Vec<i32>, calls r.len() (shared reborrow), then r.push(0) (mutable reborrow), then r.len() again — add comments showing the reborrow sequence.
  • Explicit reborrow function: Implement fn peek_then_pop(v: &mut Vec<i32>) -> Option<(i32, i32)> that reads v.last() (shared reborrow), then calls v.pop() (mutable), returning both values.
  • Nested method chain: Create a Builder struct with &mut self methods that mutate fields and return &mut Self — demonstrate chaining four method calls in one expression.
  • Open Source Repos