# Rust reference diagrams

Templates for explaining Rust ownership, borrowing, lifetimes, and smart pointers using the same diagram conventions from SKILL.md. Load this file whenever the topic involves Rust concepts like `Box`, `Rc`, `Arc`, `RefCell`, `Mutex`, Lifetimes, `move`, `Drop`, trait objects, or borrow-checker errors.

Each template below is a starting shape, not a fill-in-the-blank form - adapt variable names, types, and exact panic/error messages to what the user's actual code does. Don't reuse a template verbatim if it doesn't match their snippet.

---

## Ownership move

```text
let s1 = String::from("hello");
  |
  v
① s1 owns the heap data
  - stack: s1 ( ptr, len, cap )
  - heap: "hello"
  |
  v
② let s2 = s1;  - MOVE happens here
  - ptr/len/cap bitwise-copied to s2
  - s1 is marked invalid by the compiler
  - NO heap data is copied or freed
  |
  v
③ println!("{}", s1);  x compile error
  -> "value borrowed here after move"
  - only s2 may be used from this point on
  |
  v
④ s2 goes out of scope
  -> Drop::drop(s2) runs -> heap memory freed

```

Prose to add: name this as **move semantics**, and note the key trick - nothing runs at runtime for the move itself (no clone, no free); the compiler statically forbids using `s1` afterward. Contrast with `s1.clone()`, which would branch step ② into an actual heap allocation instead of an invalidation.

---

## Borrowing & Lifetimes

```text
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
  |
  v
① Caller creates data
  - let s1 = String::from("long string");
  - let s2 = String::from("short");
  |
  v
② Caller takes references
  - let r1 = &s1;  (borrow starts, s1 not moved)
  - let r2 = &s2;
  |
  v
③ longest(r1, r2) called
  - compiler unifies 'a to the SHORTER of the two borrows' scopes
  - both r1 and r2 must outlive 'a
  - returned reference is tied to 'a too
  |
  +------------------+-------------------+
  |                                      |
both borrows valid               one borrow ends early
through the return               (e.g. s2 dropped before use)
  |                                      |
  v                                      v
④ result used safely             x compile error
  while r1/r2 in scope           "borrowed value does not live long enough"

```

Prose to add: clarify that lifetimes are **not runtime values** - nothing in this diagram executes; `'a` is a compile-time constraint the borrow checker solves once, at step ③. The branch is a compile-time proof, not a runtime fork - flag that distinction since it's the most common source of confusion (people expect lifetimes to "do" something at runtime).

---

## `Box<T>` (heap allocation, single owner)

```text
let boxed = Box::new(MyStruct { .. });
  |
  v
① Box::new(value) called
  - heap: allocate space, move 'value' in
  - stack: boxed ( ptr ) (just a pointer, 8 bytes)
  |
  v
② Access via boxed.field or *boxed
  - auto-deref: compiler inserts *boxed transparently
  |
  v
③ boxed goes out of scope
  -> Drop::drop(boxed) runs
  - heap memory deallocated immediately (no refcount involved)

```

Prose to add: `Box<T>` is the simplest smart pointer - single owner, deterministic deallocation, no shared-ownership machinery. Good baseline to contrast against `Rc`/`Arc` below.

---

## `Rc<T>` (shared ownership, single-threaded)

```text
let a = Rc::new(5);
  |
  v
① Rc::new(5) called
  - heap: ( value: 5, strong: 1, weak: 0 )
  - a -> points to heap block
  |
  v
② let b = Rc::clone(&a);  - NOT a deep copy
  - heap: strong count 1 -> 2
  - b points to the SAME heap block as a
  |
  v
③ drop(a);
  - heap: strong count 2 -> 1
  - value NOT freed (b still holds a reference)
  |
  v
④ drop(b);
  - heap: strong count 1 -> 0
  |
  v
  value deallocated now - only when the LAST strong ref drops

```

Prose to add: name this the **shared-ownership / reference-counting pattern**. Flag step ③ as the part people trip on - `drop(a)` doesn't free anything if the count isn't zero, which surprises people coming from languages with single-owner-by-default semantics. Mention `Rc` is not thread-safe (`!Send`) as a segue into `Arc` if relevant.

---

## `Arc<T>` + `Mutex<T>` (shared ownership across threads)

```text
let counter = Arc::new(Mutex::new(0));
  |
  v
① Arc::new(Mutex::new(0))
  - heap: ( data: Mutex(0), strong: 1 (atomic) )
  |
  v
② let c2 = Arc::clone(&counter);
  - thread::spawn(move || { ... })
  - strong count incremented ATOMICALLY (safe across threads)
  - c2 moved into the new thread's closure
  |
  v
③ Inside spawned thread: c2.lock()  - BLOCKS HERE
  - if another thread holds the lock, this thread parks
  - once acquired, returns MutexGuard<i32>
  |
  +------------------+-------------------+
  |                                      |
lock acquired                    lock poisoned
(no thread panicked              (a thread panicked
 while holding it)                while holding the lock)
  |                                      |
  v                                      v
④ *guard += 1;                  .lock() returns Err(PoisonError)
  guard dropped                 -> caller must .unwrap() or handle it
  -> lock released                 explicitly
  |
  v
⑤ All threads joined
  - Arc strong count drops to 0 as each clone is dropped
  - Mutex and inner value deallocated

```

Prose to add: this is **shared mutable state across threads**, combining atomic reference counting (`Arc`) with mutual exclusion (`Mutex`). The blocking step is ③ - call that out explicitly, and mention poisoning (④'s branch) as the Rust-specific gotcha: a panic while holding the lock poisons it for everyone else, unlike a plain re-entrant lock in other languages.

---

## `RefCell<T>` (interior mutability, single-threaded)

```text
let cell = RefCell::new(5);
  |
  v
① RefCell::new(5)
  - stack/heap: { value: 5, borrow_flag: 0 }
  - compiler allows this even behind a shared &RefCell<T>
  |
  v
② let r1 = cell.borrow();  (immutable borrow, runtime-checked)
  - borrow_flag: 0 -> 1 (shared)
  |
  v
③ let r2 = cell.borrow_mut(); - RUNTIME CHECK, not compile-time
  - checks borrow_flag before allowing a mutable borrow
  |
  +------------------+-------------------+
  |                                      |
flag was 0                      flag was already
(no active borrows)             non-zero (r1 still alive)
  |                                      |
  v                                      v
④ borrow_mut()                  panic!("already borrowed:
  succeeds                      BorrowedMutError")
  flag: 0 -> -1(mut)

```

Prose to add: unlike `&`/`&mut` which the compiler checks at compile time, `RefCell` moves that check to **runtime** - call this the key trade-off. The branch at step ③ is the part to emphasize: violating borrow rules with a `RefCell` doesn't fail to compile, it panics while running, which is why people say "RefCell trades compile errors for runtime panics."

---

## Trait objects / dynamic dispatch (`dyn Trait`)

```text
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Circle), Box::new(Square)];
  |
  v
① Box::new(Circle) / Box::new(Square)
  - each heap allocation stores:
    - the concrete data (Circle or Square)
    - a vtable pointer for its Shape impl
  |
  v
② shapes.iter() loop
  - for shape in &shapes { shape.area(); }
  |
  v
③ shape.area() called  - DYNAMIC DISPATCH
  - runtime looks up 'area' in this object's vtable
  - jumps to Circle::area or Square::area
    depending on which is actually stored
  - (contrast: generic fn with 'impl Shape' is resolved
     at COMPILE time via monomorphization - no vtable lookup)

```

Prose to add: name this **dynamic dispatch via vtables**, and use step ③'s parenthetical to contrast with static dispatch (generics), since that comparison is usually what motivated the question in the first place.

---

## `Drop` and RAII cleanup order

```text
{
    let a = Resource::new("A");
    let b = Resource::new("B");
    ...
} // scope ends
  |
  v
① a and b constructed, in that order
  - stack holds a, then b
  |
  v
② scope exits (end of block, early return, or panic unwind)
  - Drop runs in REVERSE order of construction
  |
  v
③ Drop::drop(&mut b) called first
  |
  v
④ Drop::drop(&mut a) called second

```

Prose to add: this is **RAII (resource acquisition is initialization)** - call out that reverse-order drop is deterministic and holds even during a panic unwind (unless the panic itself happens inside a `Drop` impl, which aborts).

---

## Applying these to the user's actual code

When the user shares a real snippet:

* Trace their actual variable names, types, and function names - don't leave the template's placeholder names in.
* If their code mixes patterns (e.g. `Arc<Mutex<Vec<T>>>` or a `Rc<RefCell<T>>` graph), draw ONE diagram that reflects the combination rather than two separate template diagrams stapled together.
* If they're debugging a real borrow-checker error, put the actual compiler error text in the branch that leads to the error, not a generic placeholder.
