🦀 Functional Rust
🎬 Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
📝 Text version (for readers / accessibility)

• Each value in Rust has exactly one owner — when the owner goes out of scope, the value is dropped

• Assignment moves ownership by default; the original binding becomes invalid

• Borrowing (&T / &mut T) lets you reference data without taking ownership

• The compiler enforces: many shared references OR one mutable reference, never both

• No garbage collector needed — memory is freed deterministically at scope exit

111: RefCell\<T\> — Runtime Borrow Checking

Difficulty: 2 Level: Intermediate `RefCell<T>` enforces Rust's borrowing rules at runtime instead of compile time — enabling interior mutability for non-`Copy` types when the borrow checker can't see the pattern is safe.

The Problem This Solves

Sometimes you're writing a data structure where the ownership pattern is correct but the borrow checker can't prove it. A tree traversal that modifies nodes as it visits them. A mock object in tests that records calls made to it while appearing immutable to the code being tested. A recursive data structure where a node needs to reference its parent. The borrow checker proves safety conservatively — if it can't prove something is safe at compile time, it rejects it. `RefCell<T>` is the escape hatch for cases where you, the programmer, know the borrow rules are respected at runtime even though the compiler can't verify it statically. Unlike `Cell<T>` (which only works for `Copy` types by copying values), `RefCell<T>` hands out actual references to its interior — but tracks them at runtime. At any point, there can be either multiple `Ref<T>` (shared borrows) or one `RefMut<T>` (exclusive borrow), but not both. If you violate this, the program panics — same rule as the borrow checker, just checked when the borrow happens rather than at compile time.

The Intuition

`RefCell<T>` moves the borrow checker's "one writer or multiple readers" rule from compile time to runtime — you get interior mutability for any type, paying a small runtime cost and risking a panic if you accidentally break the rule.

How It Works in Rust

use std::cell::RefCell;

// Mock logger: must appear immutable to callers, but records internally
struct MockLogger {
 log: RefCell<Vec<String>>,
}

impl MockLogger {
 fn new() -> Self {
     MockLogger { log: RefCell::new(vec![]) }
 }
 
 fn write(&self, msg: &str) { // &self — looks immutable
     self.log.borrow_mut().push(msg.to_string()); // runtime check: get exclusive borrow
 }
 
 fn entries(&self) -> Vec<String> {
     self.log.borrow().clone() // runtime check: get shared borrow
 }
}

fn demo() {
 let logger = MockLogger::new();
 logger.write("first");
 logger.write("second");
 println!("{:?}", logger.entries()); // ["first", "second"]
}

// Runtime panic if borrow rules violated
fn demo_panic() {
 let cell = RefCell::new(vec![1, 2, 3]);
 
 let borrow1 = cell.borrow();      // shared borrow — ok
 let borrow2 = cell.borrow();      // second shared borrow — ok
 // let borrow_mut = cell.borrow_mut(); // PANIC: already borrowed as immutable
 
 drop(borrow1);
 drop(borrow2);
 let mut borrow_mut = cell.borrow_mut(); // now ok — no shared borrows
 borrow_mut.push(4);
}

// try_borrow returns Result instead of panicking
fn safe_borrow(cell: &RefCell<Vec<i32>>) {
 match cell.try_borrow_mut() {
     Ok(mut v) => v.push(99),
     Err(_) => eprintln!("Already borrowed — skip"),
 }
}

// Common pattern: Rc<RefCell<T>> for shared mutable ownership
use std::rc::Rc;
let shared = Rc::new(RefCell::new(0));
let clone = Rc::clone(&shared);
*clone.borrow_mut() += 1;
println!("{}", shared.borrow()); // 1

What This Unlocks

Key Differences

ConceptOCamlRust
Mutable state in immutable context`ref` type — always available`RefCell<T>` — explicit opt-in
When rules are checkedN/A (GC, no borrow rules)Compile time (normal) vs runtime (`RefCell`)
Violation consequenceN/ARuntime panic (vs compile error normally)
For Copy typesN/APrefer `Cell<T>` — lighter, no references
Thread safetyN/A`RefCell` is NOT thread-safe; use `Mutex<T>` for threads
// Example 111: RefCell<T> Runtime Borrow Checking
//
// RefCell<T> enforces borrowing rules at runtime instead of compile time.
// Panics if you violate: multiple &mut or &mut + & simultaneously.

use std::cell::RefCell;

// Approach 1: Mutable collection through shared reference
fn approach1() {
    let items: RefCell<Vec<String>> = RefCell::new(Vec::new());
    items.borrow_mut().push("first".into());
    items.borrow_mut().push("second".into());
    items.borrow_mut().push("third".into());
    
    let borrowed = items.borrow();
    assert_eq!(borrowed.len(), 3);
    println!("Items: {}", borrowed.join(", "));
}

// Approach 2: Shared mutable stack
struct Stack<T> {
    data: RefCell<Vec<T>>,
}

impl<T: std::fmt::Debug> Stack<T> {
    fn new() -> Self { Stack { data: RefCell::new(Vec::new()) } }
    fn push(&self, val: T) { self.data.borrow_mut().push(val); }
    fn pop(&self) -> Option<T> { self.data.borrow_mut().pop() }
    fn peek(&self) -> Option<String> {
        self.data.borrow().last().map(|v| format!("{:?}", v))
    }
}

fn approach2() {
    let s = Stack::new();
    s.push(1);
    s.push(2);
    s.push(3);
    assert_eq!(s.peek(), Some("3".into()));
    assert_eq!(s.pop(), Some(3));
    assert_eq!(s.pop(), Some(2));
    println!("Remaining top: {}", s.peek().unwrap());
}

// Approach 3: Observer pattern
struct Subject {
    observers: RefCell<Vec<Box<dyn Fn(&str)>>>,
}

impl Subject {
    fn new() -> Self { Subject { observers: RefCell::new(Vec::new()) } }
    fn add_observer(&self, f: Box<dyn Fn(&str)>) {
        self.observers.borrow_mut().push(f);
    }
    fn notify_all(&self, msg: &str) {
        for obs in self.observers.borrow().iter() {
            obs(msg);
        }
    }
}

fn approach3() {
    let log: RefCell<Vec<String>> = RefCell::new(Vec::new());
    let subject = Subject::new();
    
    // We need Rc for shared access to log from multiple closures
    use std::rc::Rc;
    let log = Rc::new(log);
    
    let log1 = Rc::clone(&log);
    subject.add_observer(Box::new(move |msg| {
        log1.borrow_mut().push(msg.to_string());
    }));
    
    let log2 = Rc::clone(&log);
    subject.add_observer(Box::new(move |msg| {
        log2.borrow_mut().push(format!("copy:{}", msg));
    }));
    
    subject.notify_all("hello");
    assert_eq!(log.borrow().len(), 2);
    println!("Notifications: {}", log.borrow().len());
}

fn main() {
    println!("=== Approach 1: Mutable Collection ===");
    approach1();
    println!("\n=== Approach 2: Shared Mutable Stack ===");
    approach2();
    println!("\n=== Approach 3: Observer Pattern ===");
    approach3();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_refcell_borrow() {
        let cell = RefCell::new(vec![1, 2, 3]);
        assert_eq!(cell.borrow().len(), 3);
        cell.borrow_mut().push(4);
        assert_eq!(cell.borrow().len(), 4);
    }

    #[test]
    #[should_panic]
    fn test_refcell_double_mut_panics() {
        let cell = RefCell::new(42);
        let _a = cell.borrow_mut();
        let _b = cell.borrow_mut(); // PANIC: already borrowed
    }

    #[test]
    fn test_stack() {
        let s = Stack::new();
        s.push(10);
        s.push(20);
        assert_eq!(s.pop(), Some(20));
        assert_eq!(s.pop(), Some(10));
        assert_eq!(s.pop(), None);
    }

    #[test]
    fn test_try_borrow() {
        let cell = RefCell::new(42);
        let _r = cell.borrow();
        assert!(cell.try_borrow_mut().is_err()); // can't mut borrow while shared
    }
}
(* Example 111: RefCell<T> — Runtime Borrow Checking *)

(* OCaml doesn't have borrow checking. Mutable data is always accessible.
   RefCell adds Rust's borrow rules at runtime for non-Copy types. *)

(* Approach 1: Mutable list inside immutable binding *)
let approach1 () =
  let items = ref [] in
  items := "first" :: !items;
  items := "second" :: !items;
  items := "third" :: !items;
  let result = List.rev !items in
  assert (result = ["first"; "second"; "third"]);
  Printf.printf "Items: %s\n" (String.concat ", " result)

(* Approach 2: Shared mutable state *)
type 'a stack = { mutable data : 'a list }

let push s x = s.data <- x :: s.data
let pop s = match s.data with
  | [] -> None
  | x :: rest -> s.data <- rest; Some x
let peek s = match s.data with [] -> None | x :: _ -> Some x

let approach2 () =
  let s = { data = [] } in
  push s 1; push s 2; push s 3;
  assert (peek s = Some 3);
  assert (pop s = Some 3);
  assert (pop s = Some 2);
  Printf.printf "Remaining top: %d\n" (Option.get (peek s))

(* Approach 3: Observer pattern with mutable callbacks *)
type observer = { notify : string -> unit }
type subject = { mutable observers : observer list }

let add_observer subj obs = subj.observers <- obs :: subj.observers
let notify_all subj msg = List.iter (fun o -> o.notify msg) subj.observers

let approach3 () =
  let log = ref [] in
  let subj = { observers = [] } in
  add_observer subj { notify = fun msg -> log := msg :: !log };
  add_observer subj { notify = fun msg -> log := ("copy:" ^ msg) :: !log };
  notify_all subj "hello";
  assert (List.length !log = 2);
  Printf.printf "Notifications: %d\n" (List.length !log)

let () =
  approach1 ();
  approach2 ();
  approach3 ();
  Printf.printf "✓ All tests passed\n"

📊 Detailed Comparison

Comparison: RefCell Runtime Borrow Checking

Mutable Collection

OCaml:

🐪 Show OCaml equivalent
let items = ref [] in
items := "first" :: !items;
items := "second" :: !items;
List.rev !items

Rust:

let items = RefCell::new(Vec::new());
items.borrow_mut().push("first".into());
items.borrow_mut().push("second".into());
items.borrow() // returns Ref<Vec<String>>

Shared Mutable State

OCaml:

🐪 Show OCaml equivalent
type 'a stack = { mutable data : 'a list }
let push s x = s.data <- x :: s.data

Rust:

struct Stack<T> { data: RefCell<Vec<T>> }
impl<T> Stack<T> {
 fn push(&self, val: T) { self.data.borrow_mut().push(val); }
}

Borrow Violation

OCaml — no protection:

🐪 Show OCaml equivalent
(* aliasing mutable data is allowed but dangerous *)

Rust — panics at runtime:

let cell = RefCell::new(42);
let _a = cell.borrow_mut();
let _b = cell.borrow_mut(); // PANIC!