🦀 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

108: Rc\<T\> — Shared Ownership

Difficulty: 2 Level: Intermediate `Rc<T>` enables multiple owners for the same value in single-threaded code — Rust's explicit, opt-in equivalent of garbage collection.

The Problem This Solves

Rust's default ownership model — one owner, automatic drop at end of scope — solves most problems cleanly. But some data structures genuinely need shared ownership: a graph where multiple nodes reference the same edge, a GUI widget tree where parent and child both need a reference to shared state, a cache where the same value is referenced from multiple call sites. In C, you'd use raw pointers and manage lifetimes manually — tedious and error-prone, leading to double-frees (freeing the same memory twice) and use-after-free bugs. In Python or OCaml, the GC handles this automatically, but you pay the GC overhead for everything, even values that don't need sharing. Rust gives you `Rc<T>`: explicit, opt-in reference counting. The `Rc` (Reference Counted) smart pointer tracks how many clones exist. When the last one drops, the value is freed. No GC, no manual tracking. You pay for reference counting only when you actually need shared ownership.

The Intuition

`Rc<T>` is reference counting you explicitly opt into — clone the `Rc` to share ownership, and the value is freed automatically when the last `Rc` to it is dropped.

How It Works in Rust

use std::rc::Rc;

fn demo_basic_sharing() {
 let data = Rc::new(String::from("shared value"));
 
 // Clone the Rc — this clones the *pointer*, not the data
 let owner1 = Rc::clone(&data);  // reference count: 2
 let owner2 = Rc::clone(&data);  // reference count: 3
 
 println!("Count: {}", Rc::strong_count(&data)); // 3
 println!("{}", owner1);  // "shared value"
 println!("{}", owner2);  // "shared value"
 
 drop(owner1); // count: 2
 drop(owner2); // count: 1
 // data dropped here: count goes to 0, String is freed
}

// Tree where multiple nodes share a parent
#[derive(Debug)]
enum Tree {
 Leaf(i32),
 Node(i32, Rc<Tree>, Rc<Tree>),
}

fn demo_tree() {
 let shared_leaf = Rc::new(Tree::Leaf(5));
 
 // Both branches share the same leaf — no copying
 let branch1 = Tree::Node(1, Rc::clone(&shared_leaf), Rc::clone(&shared_leaf));
 println!("Shared leaf referenced {} times", Rc::strong_count(&shared_leaf));
}

// Rc<T> is immutable — no mutation through shared ownership
// Use Rc<RefCell<T>> when you need shared mutable ownership
use std::cell::RefCell;
let shared_mutable = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone = Rc::clone(&shared_mutable);
clone.borrow_mut().push(4);  // mutate through shared ownership
println!("{:?}", shared_mutable.borrow()); // [1, 2, 3, 4]

What This Unlocks

Key Differences

ConceptOCamlRust
Shared ownershipAutomatic (GC manages all values)Explicit — wrap in `Rc<T>` to opt in
Reference countingBuilt into GCManual via `Rc<T>`
Thread safetyGC is thread-safe`Rc<T>` is single-threaded only; use `Arc<T>` for threads
MutationMutable fields freely`Rc<T>` is immutable; combine with `RefCell<T>` for mutation
Reference cyclesGC handles (or weak refs needed)Can leak memory; use `Weak<T>` to break cycles
// Example 108: Rc<T> for Shared Ownership
//
// Rc<T> = Reference Counted pointer. Multiple owners of the same data.
// Like OCaml's default GC sharing, but explicit and single-threaded.

use std::rc::Rc;

// Approach 1: Shared tree nodes with Rc
#[derive(Debug)]
enum Tree {
    Leaf,
    Node(Rc<Tree>, i32, Rc<Tree>),
}

fn tree_sum(t: &Tree) -> i32 {
    match t {
        Tree::Leaf => 0,
        Tree::Node(l, v, r) => tree_sum(l) + v + tree_sum(r),
    }
}

fn approach1() {
    let shared = Rc::new(Tree::Node(
        Rc::new(Tree::Leaf), 42, Rc::new(Tree::Leaf),
    ));
    // Clone Rc = increment reference count (cheap!)
    let tree1 = Tree::Node(Rc::clone(&shared), 1, Rc::new(Tree::Leaf));
    let tree2 = Tree::Node(Rc::new(Tree::Leaf), 2, Rc::clone(&shared));
    
    println!("Rc strong count: {}", Rc::strong_count(&shared));
    assert_eq!(tree_sum(&tree1), 43);
    assert_eq!(tree_sum(&tree2), 44);
    println!("Tree1 sum: {}, Tree2 sum: {}", tree_sum(&tree1), tree_sum(&tree2));
}

// Approach 2: Shared data in multiple collections
fn approach2() {
    let shared = Rc::new(vec![3, 4, 5]);
    let collection_a = (vec![1, 2], Rc::clone(&shared));
    let collection_b = (vec![10], Rc::clone(&shared));
    
    assert_eq!(Rc::strong_count(&shared), 3); // original + 2 clones
    println!("Shared vec: {:?}, refs: {}", shared, Rc::strong_count(&shared));
    println!("A: {:?} + {:?}", collection_a.0, collection_a.1);
    println!("B: {:?} + {:?}", collection_b.0, collection_b.1);
}

// Approach 3: DAG with Rc
#[derive(Debug)]
struct DagNode {
    id: i32,
    children: Vec<Rc<DagNode>>,
}

fn approach3() {
    let shared = Rc::new(DagNode { id: 3, children: vec![] });
    let a = Rc::new(DagNode { id: 1, children: vec![Rc::clone(&shared)] });
    let b = Rc::new(DagNode { id: 2, children: vec![Rc::clone(&shared)] });
    let root = DagNode { id: 0, children: vec![a, b] };
    
    assert_eq!(root.id, 0);
    assert_eq!(root.children.len(), 2);
    assert_eq!(Rc::strong_count(&shared), 3); // a, b, and shared
    println!("DAG root {} has {} children, shared refs: {}",
        root.id, root.children.len(), Rc::strong_count(&shared));
}

fn main() {
    println!("=== Approach 1: Shared Tree Nodes ===");
    approach1();
    println!("\n=== Approach 2: Shared Collections ===");
    approach2();
    println!("\n=== Approach 3: DAG ===");
    approach3();
}

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

    #[test]
    fn test_rc_clone_is_cheap() {
        let data = Rc::new(String::from("hello"));
        let clone = Rc::clone(&data);
        assert_eq!(*data, *clone);
        assert_eq!(Rc::strong_count(&data), 2);
    }

    #[test]
    fn test_rc_drop_decrements() {
        let data = Rc::new(42);
        let clone = Rc::clone(&data);
        assert_eq!(Rc::strong_count(&data), 2);
        drop(clone);
        assert_eq!(Rc::strong_count(&data), 1);
    }

    #[test]
    fn test_shared_tree() {
        let leaf = Rc::new(Tree::Leaf);
        let node = Tree::Node(Rc::clone(&leaf), 10, Rc::clone(&leaf));
        assert_eq!(tree_sum(&node), 10);
    }

    #[test]
    fn test_dag_shared_child() {
        let child = Rc::new(DagNode { id: 1, children: vec![] });
        let p1 = DagNode { id: 2, children: vec![Rc::clone(&child)] };
        let p2 = DagNode { id: 3, children: vec![Rc::clone(&child)] };
        assert_eq!(Rc::strong_count(&child), 3);
        drop(p1);
        assert_eq!(Rc::strong_count(&child), 2);
        drop(p2);
        assert_eq!(Rc::strong_count(&child), 1);
    }
}
(* Example 108: Rc<T> — OCaml Default Sharing → Rust Explicit Rc *)

(* In OCaml, all values are implicitly reference-counted/GC-managed.
   Multiple bindings to the same value is the default behavior. *)

(* Approach 1: Shared tree nodes *)
type 'a tree = Leaf | Node of 'a tree * 'a * 'a tree

let shared_subtree () =
  let shared = Node (Leaf, 42, Leaf) in
  let tree1 = Node (shared, 1, Leaf) in
  let tree2 = Node (Leaf, 2, shared) in
  (* shared is used in both trees — GC keeps it alive *)
  (tree1, tree2)

let rec tree_sum = function
  | Leaf -> 0
  | Node (l, v, r) -> tree_sum l + v + tree_sum r

let approach1 () =
  let (t1, t2) = shared_subtree () in
  let s1 = tree_sum t1 in
  let s2 = tree_sum t2 in
  Printf.printf "Tree1 sum: %d, Tree2 sum: %d\n" s1 s2;
  assert (s1 = 43);
  assert (s2 = 44)

(* Approach 2: Shared list nodes — cons sharing *)
let approach2 () =
  let shared_tail = [3; 4; 5] in
  let list_a = 1 :: 2 :: shared_tail in
  let list_b = 10 :: shared_tail in
  assert (List.length list_a = 5);
  assert (List.length list_b = 4);
  Printf.printf "list_a length: %d, list_b length: %d\n"
    (List.length list_a) (List.length list_b)

(* Approach 3: DAG (directed acyclic graph) via sharing *)
type node = { id : int; children : node list }

let approach3 () =
  let shared = { id = 3; children = [] } in
  let a = { id = 1; children = [shared] } in
  let b = { id = 2; children = [shared] } in
  let root = { id = 0; children = [a; b] } in
  assert (root.id = 0);
  assert (List.length root.children = 2);
  Printf.printf "DAG root %d has %d children\n" root.id (List.length root.children)

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

📊 Detailed Comparison

Comparison: Rc\<T\> Shared Ownership

Shared Tree Nodes

OCaml — implicit sharing:

🐪 Show OCaml equivalent
let shared = Node (Leaf, 42, Leaf) in
let t1 = Node (shared, 1, Leaf) in
let t2 = Node (Leaf, 2, shared) in
(* shared is in both trees — GC manages it *)

Rust — explicit `Rc`:

let shared = Rc::new(Tree::Node(Rc::new(Tree::Leaf), 42, Rc::new(Tree::Leaf)));
let t1 = Tree::Node(Rc::clone(&shared), 1, Rc::new(Tree::Leaf));
let t2 = Tree::Node(Rc::new(Tree::Leaf), 2, Rc::clone(&shared));

Reference Counting

OCaml — invisible:

🐪 Show OCaml equivalent
let x = [1; 2; 3] in
let y = x in  (* GC tracks references internally *)

Rust — observable:

let x = Rc::new(vec![1, 2, 3]);
let y = Rc::clone(&x);
println!("refs: {}", Rc::strong_count(&x)); // 2
drop(y);
println!("refs: {}", Rc::strong_count(&x)); // 1