Build the mental model
Strictly following Lesson 6's ownership rule ("each value has exactly one owner") makes some real-world data structures — linked lists, trees, graphs, shared caches — genuinely hard to express; the three smart pointers here are escape hatches for that limitation. `Box<T>` is the simplest: it allocates a value on the heap while still following the ownership rules, useful for recursive types the compiler can't size at compile time (like a tree-node struct that needs a field of its own type) — `Box`'s fixed pointer size gives the compiler a known compile-time size. `Rc<T>` (Reference Counted) relaxes Lesson 6's "exactly one owner" rule — it lets multiple owners share the same value, tracking an internal count that drops the value only once it reaches zero — and it's safe only in single-threaded contexts. `RefCell<T>` moves the borrow checker's compile-time check to a runtime check instead — an "interior mutability" pattern that lets you mutate a value's inner content via `.borrow_mut()` even while only holding an immutable reference (`&self`), enforcing Lesson 7's borrow rule at runtime instead of compile time — violating it causes a runtime panic instead of a compile error. Combining `Rc<RefCell<T>>` lets multiple owners share and mutate a value in a single-threaded context, a pattern you'll meet again as the multi-threaded equivalent, `Arc<Mutex<T>>`, in Lesson 18.
Connect it to a real scenario
To model the content analyzer's lesson tree structure (a topic tree of sub-topics inside a chapter), use `Box`: `struct TopicNode { name: String, children: Vec<Box<TopicNode>> }` — wrapping the recursive struct in `Box` gives the compiler a known compile-time size. To share a tag cache across multiple analyzer functions, use `Rc<Vec<String>>` — every `.clone()` call bumps only the reference count instead of deep-copying the underlying `Vec`, keeping it memory-efficient. To share and update a running-total counter across multiple functions, use `Rc<RefCell<u32>>` — mutate it with `counter.borrow_mut()`, getting Lesson 7's borrow rule enforced at runtime instead.
Try the working example
use std::rc::Rc;
use std::cell::RefCell;
struct TopicNode {
name: String,
children: Vec<Box<TopicNode>>,
}
fn main() {
let leaf = Box::new(TopicNode { name: String::from("Ownership"), children: vec![] });
let root = TopicNode { name: String::from("Basics"), children: vec![leaf] };
println!("{} -> {}", root.name, root.children[0].name);
let counter = Rc::new(RefCell::new(0));
let counter_clone = Rc::clone(&counter);
*counter_clone.borrow_mut() += 5;
println!("shared counter: {}", counter.borrow());
println!("owners: {}", Rc::strong_count(&counter));
}You print "Basics -> Ownership", "shared counter: 5", and "owners: 2".5-minute try-it
Use `Rc<RefCell<Vec<String>>>` to create a shared tag list — clone it to create two owners, push a tag from one owner (`.borrow_mut().push(...)`), and check whether the change is visible from the other owner.
One important caution
Trying to use `Rc<T>` across threads — `Rc`'s reference counter isn't thread-safe, and the compiler blocks it with a compile error; the multi-threaded equivalent `Arc<T>` (atomic reference count) from Lesson 18 is needed instead.
Calling `.borrow_mut()` on a `RefCell<T>` again while a borrow from it is already active in the same scope — instead of a compile error, this triggers an "already borrowed" runtime panic, since RefCell's safety check only happens at runtime and can't catch this mistake ahead of time.