16
Layout
Persistent lists share suffixes. List B = 1 -> 2 -> 3 and list C = 0 -> 2 -> 3 should both point at the same 2 -> 3 tail in memory; freeing B must not free a node C is still using. Box<T> is a single owner, so it can't model this. Rust's answer is Rc<T> — a shared owner with an embedded refcount, the safe-Rust analogue of std::shared_ptr.
-
Rc<T>(reference-counted) is a heap allocation with a count next to the value.Rc::clonebumps the count; theDropof anRcdecrements it; the last drop frees the allocation. Conceptually identical tostd::shared_ptr<T>minus the atomics — single-threaded, so the count is a plain integer.use std::rc::Rc; pub struct List<T> { head: Link<T>, } type Link<T> = Option<Rc<Node<T>>>; struct Node<T> { elem: T, next: Link<T>, } - Two lists can now point at the same
Rc<Node<T>>. Clone theRcand the count goes from 1 to 2; both lists hold a handle; whichever drops second runs the destructor. We're usingOption<Rc<Node<T>>>rather than a hand-rolledLinkenum — last chapter taught us that.// list_a: 1 -> 2 -> 3 -> Nil // list_b: 0 -> 2 -> 3 -> Nil (shares the 2->3 tail) // // the node holding `2`: // refcount = 2 (list_a's node-1 points at it; list_b's node-0 points at it) // the node holding `3`: // refcount = 1 (only the node holding `2` points at it) - One catch:
Rc<T>only hands out shared references. There is noRc::get_mutonce the count is above one, because mutation through a shared owner would be a data race waiting to happen. Persistence is the natural fit — we never mutate; we build new lists that share old tails. (For interior mutability underRcyou'd reach forRefCell, which is the next chapter's mistake.)let shared: Rc<i32> = Rc::new(42); let a = Rc::clone(&shared); // refcount = 2 let b = Rc::clone(&shared); // refcount = 3 // *a = 99; // error: cannot assign through `&` reference // // Rc only gives out shared access.