🦀 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

110: Cell\<T\> — Interior Mutability for Copy Types

Difficulty: 2 Level: Intermediate `Cell<T>` lets you mutate a value through a shared reference — for `Copy` types — by moving values in and out instead of handing out references.

The Problem This Solves

Rust's borrow checker enforces a strict rule: if you have a shared reference (`&T`), the value is read-only. This is correct almost all the time — but not always. Sometimes you have a logically immutable data structure where one field needs to be updated as a side effect: a cache that stores the last computed result, a hit counter on an otherwise-immutable record, lazy initialization of a derived value. Without interior mutability, you'd be forced to make the entire struct mutable (`&mut T`), even though you only want to mutate one field. That removes the ability to share the struct through multiple `&T` references simultaneously. The whole thing becomes awkward. `Cell<T>` solves this for `Copy` types. Instead of handing out references to the inner value (which would violate borrow rules), `Cell` lets you only copy values in and out. The `set` method replaces the value; `get` copies it out. No references to the interior are ever created, so there's no aliasing — the borrow checker's rule is respected, not worked around.

The Intuition

`Cell<T>` sidesteps the borrow checker for `Copy` types by never handing out references to the interior — you can only swap values in and out, making mutation through a shared reference safe by design.

How It Works in Rust

use std::cell::Cell;

struct HitCounter {
 label: String,
 hits: Cell<u32>, // mutable through &self, no &mut needed
}

impl HitCounter {
 fn new(label: &str) -> Self {
     HitCounter {
         label: label.to_string(),
         hits: Cell::new(0),
     }
 }
 
 fn record_hit(&self) { // note: &self, not &mut self
     self.hits.set(self.hits.get() + 1);
 }
 
 fn count(&self) -> u32 {
     self.hits.get()
 }
}

fn demo() {
 let counter = HitCounter::new("homepage");
 
 // Multiple shared references — normally can't mutate through these
 let r1 = &counter;
 let r2 = &counter;
 
 r1.record_hit(); // mutation through shared ref — Cell makes this safe
 r2.record_hit();
 
 println!("{}: {} hits", counter.label, counter.count()); // 2 hits
}

// Cell<T> with flag types
use std::cell::Cell as Flag;
struct LazyNode {
 value: i32,
 computed: Cell<bool>,
}

impl LazyNode {
 fn mark_computed(&self) {
     self.computed.set(true);
 }
 
 fn is_computed(&self) -> bool {
     self.computed.get()
 }
}

// Cell<T> only works for Copy types
// Cell<String> would not work — String is not Copy
// For non-Copy types, use RefCell<T> (example 111)

What This Unlocks

Key Differences

ConceptOCamlRust
Mutable field in immutable record`mutable` field + `ref``Cell<T>` for `Copy` types
OCaml ref equivalent`ref` (always available)`Cell<T>` (explicit, only for `Copy`)
Mutation through shared accessFreely availableRequires `Cell` or `RefCell`
Thread safetyNot applicable (single-threaded model common)`Cell` is NOT thread-safe; use `AtomicU32` etc. for threads
Non-Copy typesAll types are GC-managedUse `RefCell<T>` instead (example 111)
// Example 110: Cell<T> Interior Mutability
//
// Cell<T> allows mutation through a shared reference (&T).
// Works only with Copy types. No runtime borrow checking overhead.

use std::cell::Cell;

// Approach 1: Simple mutable counter
fn approach1() {
    let counter = Cell::new(0);
    counter.set(counter.get() + 1);
    counter.set(counter.get() + 1);
    assert_eq!(counter.get(), 2);
    println!("Counter: {}", counter.get());
}

// Approach 2: Mutable field in an "immutable" struct
struct Config {
    name: String,
    call_count: Cell<u32>,  // interior mutability
}

fn use_config(c: &Config) {
    c.call_count.set(c.call_count.get() + 1);
    println!("Config '{}' used {} times", c.name, c.call_count.get());
}

fn approach2() {
    let c = Config { name: "default".into(), call_count: Cell::new(0) };
    use_config(&c);
    use_config(&c);
    assert_eq!(c.call_count.get(), 2);
}

// Approach 3: Caching with Cell<Option<T>> for Copy types
struct Lazy<F: Fn() -> i32> {
    compute: F,
    cache: Cell<Option<i32>>,
}

impl<F: Fn() -> i32> Lazy<F> {
    fn new(compute: F) -> Self {
        Lazy { compute, cache: Cell::new(None) }
    }
    
    fn get(&self) -> i32 {
        match self.cache.get() {
            Some(v) => v,
            None => {
                let v = (self.compute)();
                self.cache.set(Some(v));
                v
            }
        }
    }
}

fn approach3() {
    let calls = Cell::new(0);
    let expensive = Lazy::new(|| {
        calls.set(calls.get() + 1);
        42
    });
    let v1 = expensive.get();
    let v2 = expensive.get();
    assert_eq!(v1, 42);
    assert_eq!(v2, 42);
    assert_eq!(calls.get(), 1);
    println!("Computed once: {} (calls: {})", v1, calls.get());
}

fn main() {
    println!("=== Approach 1: Cell Counter ===");
    approach1();
    println!("\n=== Approach 2: Interior Mutability in Struct ===");
    approach2();
    println!("\n=== Approach 3: Lazy Caching ===");
    approach3();
}

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

    #[test]
    fn test_cell_basic() {
        let c = Cell::new(10);
        c.set(20);
        assert_eq!(c.get(), 20);
    }

    #[test]
    fn test_cell_in_shared_ref() {
        let c = Cell::new(0);
        let r1 = &c;
        let r2 = &c;
        r1.set(1);
        r2.set(r2.get() + 1);
        assert_eq!(c.get(), 2);
    }

    #[test]
    fn test_config_call_count() {
        let c = Config { name: "test".into(), call_count: Cell::new(0) };
        use_config(&c);
        use_config(&c);
        use_config(&c);
        assert_eq!(c.call_count.get(), 3);
    }

    #[test]
    fn test_lazy_computes_once() {
        let count = Cell::new(0);
        let lazy = Lazy::new(|| { count.set(count.get() + 1); 99 });
        assert_eq!(lazy.get(), 99);
        assert_eq!(lazy.get(), 99);
        assert_eq!(count.get(), 1);
    }
}
(* Example 110: Cell<T> Interior Mutability — OCaml ref → Rust Cell *)

(* Approach 1: Simple mutable counter with ref *)
let approach1 () =
  let counter = ref 0 in
  counter := !counter + 1;
  counter := !counter + 1;
  assert (!counter = 2);
  Printf.printf "Counter: %d\n" !counter

(* Approach 2: Mutable field in an immutable struct *)
type config = { name : string; mutable call_count : int }

let use_config c =
  c.call_count <- c.call_count + 1;
  Printf.printf "Config '%s' used %d times\n" c.name c.call_count

let approach2 () =
  let c = { name = "default"; call_count = 0 } in
  use_config c;
  use_config c;
  assert (c.call_count = 2)

(* Approach 3: Caching/memoization with ref *)
let lazy_value init =
  let cache = ref None in
  fun () ->
    match !cache with
    | Some v -> v
    | None ->
      let v = init () in
      cache := Some v;
      v

let approach3 () =
  let calls = ref 0 in
  let expensive = lazy_value (fun () -> incr calls; 42) in
  let v1 = expensive () in
  let v2 = expensive () in
  assert (v1 = 42);
  assert (v2 = 42);
  assert (!calls = 1);
  Printf.printf "Computed once: %d (calls: %d)\n" v1 !calls

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

📊 Detailed Comparison

Comparison: Cell Interior Mutability

Simple Counter

OCaml:

🐪 Show OCaml equivalent
let counter = ref 0 in
counter := !counter + 1;
Printf.printf "%d\n" !counter

Rust:

let counter = Cell::new(0);
counter.set(counter.get() + 1);
println!("{}", counter.get());

Mutable Field in Struct

OCaml:

🐪 Show OCaml equivalent
type config = { name : string; mutable call_count : int }
let use_config c = c.call_count <- c.call_count + 1

Rust:

struct Config { name: String, call_count: Cell<u32> }
fn use_config(c: &Config) {
 c.call_count.set(c.call_count.get() + 1);
}

Lazy Caching

OCaml:

🐪 Show OCaml equivalent
let cache = ref None in
fun () -> match !cache with
| Some v -> v
| None -> let v = compute () in cache := Some v; v

Rust:

struct Lazy<F> { compute: F, cache: Cell<Option<i32>> }
fn get(&self) -> i32 {
 match self.cache.get() {
     Some(v) => v,
     None => { let v = (self.compute)(); self.cache.set(Some(v)); v }
 }
}