Smart Pointers
Course 1's ownership rules — exactly one owner, borrow checking enforced entirely at compile time — are strict by design. Some real, legitimate patterns genuinely don't fit that shape. Smart pointers are Rust's controlled, still-safe way to get flexibility where it's actually needed, without abandoning safety altogether.
Box<T>: Heap Allocation With a Single Owner
The simplest smart pointer: puts a value on the heap instead of the stack, while still following ordinary single-ownership rules exactly — Box<T> itself is the single owner, moving per Course 1 Chapter 3's rules like anything else. The classic use case is a recursive type:
Without Box, Cons(i32, List) fails to compile — the compiler needs to know a fixed size for List at compile time, but a List containing a List containing a List... has no fixed size at all. Box's size is always just one pointer, regardless of what it points to, breaking the infinite recursion.
Rc<T>: Multiple Owners via Reference Counting
Course 1's rule was exactly one owner. Rc<T> ("Reference Counted") relaxes this specifically for cases where multiple parts of a program genuinely need to jointly own the same data:
Rc::clone doesn't copy the underlying data — it increments a reference count. The data is only actually dropped once the last Rc pointing to it goes out of scope, bringing the count to zero.
RefCell<T> and Interior Mutability
The borrow checker's rules (Course 1, Chapter 4) are enforced at compile time by default. RefCell<T> moves that same rule — one mutable OR many immutable borrows — to runtime instead, allowing mutation even through an immutable reference to the RefCell itself:
A genuine trade-off: more flexibility, but a violated rule moves from a compile error to a runtime panic.
Combining Rc<T> and RefCell<T>
The common, idiomatic combination — Rc<RefCell<T>> — gives multiple owners that can each mutate the shared data. This is Rust's deliberate, opt-in answer to a pattern that's simply the default in a garbage-collected language (shared mutable state) — not worse, just something you have to explicitly ask for rather than get for free:
| Compile-Time Borrow Checking (Ch.4, Course 1) | RefCell<T> (Runtime) | |
|---|---|---|
| When rules are checked | At compile time | At runtime, on each borrow()/borrow_mut() call |
| Violating the rule | Compile error | Panic |
| Flexibility | Less — some valid patterns rejected | More — patterns the compiler can't verify statically still work |
Box<T>
Single ownership, heap-allocated — no rule relaxation, just placement. Needed for recursive types.
Rc<T>
Relaxes single ownership via reference counting — multiple joint owners of the same data.
RefCell<T>
Relaxes compile-time borrow checking to runtime — mutation through an immutable reference.
Rc<RefCell<T>>
Both relaxations combined — multiple owners, each able to mutate the shared data.
Box changes nothing about the rules, only where data lives; Rc relaxes single-ownership via counting; RefCell relaxes borrow-checking from compile time to runtime. Every one of them is a deliberate, scoped exception — not a general escape hatch from Rust's safety model.
Rc<T> by itself is "the mutable shared pointer." It isn't — Rc<T>'s data is immutable by default, which is exactly why it's so often paired with RefCell<T> specifically for the mutable case. Also worth flagging ahead: Rc<T> is not thread-safe — Arc<T> (the atomic version) is what Chapter 5's concurrency material uses instead.
Coding Challenges
Explain why enum Tree { Leaf(i32), Node(Box
Write code creating an Rc
Using Rc
Chapter 4 Quick Reference
- Box<T> — single-owner heap allocation; needed for recursive types (fixed-size pointer breaks infinite size)
- Rc<T> — multiple owners via reference counting; Rc::clone increments the count, no deep copy
- Rc::strong_count(&x) — inspect how many owners currently exist
- RefCell<T> — moves borrow checking to runtime; violating the rule panics instead of failing to compile
- Rc<RefCell<T>> — the idiomatic combo: multiple owners, each able to mutate shared data
- Rc<T> alone is immutable — RefCell is what actually enables mutation through it
- Rc<T> is not thread-safe — Arc<T> is the concurrency-safe equivalent, coming next chapter
- Next chapter: Concurrency — threads, Arc<Mutex<T>>, mpsc channels, and "fearless concurrency" contrasted with Go's goroutines