Reborrowing Patterns
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
&mut T implicitly reborrows as &T when passed to a function expecting &T&*r creates a shared reference from a mutable one&mut self work through repeated reborrowing&mut references to be used multiple times in sequenceCode 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
&mut references can be used multiple times via reborrowing; without reborrowing, they would need to be moved on every use (like Box).&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]);
}
}#[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
Exercises
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.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.Builder struct with &mut self methods that mutate fields and return &mut Self — demonstrate chaining four method calls in one expression.