21.5Rc<T>
Since chapter 8, every value has had exactly one owner. That single-owner rule is what makes Rust's memory management so predictable: there's always precisely one value responsible for cleaning up, and when it goes, the value goes. But some data structures genuinely need a value to be owned by several things at once. Picture a graph where two nodes both point at a shared third node: who owns the third? Neither, and both. Rc<T>, the reference-counted smart pointer, is Rust's answer to shared ownership.
The problem single ownership can't express
Try to build a cons list (lesson 21.2) where two lists share a common tail:
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let shared = Cons(3, Box::new(Nil));
let a = Cons(1, Box::new(shared));
let b = Cons(2, Box::new(shared)); // error: shared already moved into a
}
We want both a and b to end in shared. But Box is single ownership: building a moves shared into it (chapter 8), so the line building b fails, shared is already gone. The error is the familiar E0382 "use of moved value." Box can't do this, because the whole design of Box is one owner. We need a smart pointer that allows many.
Rc: shared ownership by counting
Rc<T> (the name is "reference counted") holds a value plus a count of how many owners there currently are. Cloning an Rc doesn't copy the value, it bumps the count and hands back another handle to the same value. When an Rc handle is dropped, the count drops by one; when the count reaches zero, and only then, the value is freed. Here's the shared-tail list, working:
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let shared = Rc::new(Cons(3, Rc::new(Nil)));
let a = Cons(1, Rc::clone(&shared));
let b = Cons(2, Rc::clone(&shared));
println!("built two lists sharing a tail");
}built two lists sharing a tail
Rc::new wraps the shared tail. Rc::clone(&shared) makes a second owner for a and a third for b, each a cheap handle to the same heap-allocated Cons(3, ...). The tail isn't copied; all three Rcs point at one value, and it stays alive as long as any of them does. Single ownership couldn't express "this value belongs to both lists"; Rc can.
Rc::clone is cheap, and that's why it's spelled out
Rc::clone(&shared) only increments a counter and copies a pointer, it does not deep-copy the data, so it's fast no matter how big the value is. You could write shared.clone() and it would do the same thing, but the convention is Rc::clone(&shared) precisely to make it visually distinct from an expensive deep .clone(). When you scan code for performance, Rc::clone reads as "cheap, just another owner," while a plain .clone() might be copying a whole Vec. The explicit form is a readability choice, not a different operation.
Watching the count
You can read the current count with Rc::strong_count. Watching it rise and fall makes the mechanism concrete:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared data"));
println!("count after creating a: {}", Rc::strong_count(&a));
let b = Rc::clone(&a);
println!("count after b: {}", Rc::strong_count(&a));
{
let c = Rc::clone(&a);
println!("count after c: {}", Rc::strong_count(&a));
} // c goes out of scope here
println!("count after c's scope: {}", Rc::strong_count(&a));
}count after creating a: 1
count after b: 2
count after c: 3
count after c's scope: 2
The count climbs to 3 as b and c are cloned, then falls back to 2 when c leaves its inner scope and is dropped. When main ends, b then a drop, the count hits 0, and the String is finally freed. This is Drop (last lesson) doing the bookkeeping: each Rc's drop decrements the count, and the one that takes it to zero frees the data. Nobody decides "I'll be the one to free it"; the counting decides, automatically and exactly once.
Rc is for sharing reads, single-threaded
Two limits define where Rc fits. First, Rc<T> gives you shared immutable access: you can have many owners reading the value, but you can't mutate through an Rc (that would violate chapter 9's many-readers-or-one-writer rule, several owners means several readers). When you need shared data that's also mutable, you combine Rc with the interior mutability of the next lesson, Rc<RefCell<T>>. Second, Rc is for single-threaded code only. Its counter isn't safe to update from multiple threads at once, so the compiler won't let an Rc cross a thread boundary. Chapter 22 introduces Arc<T>, the atomically-counted sibling, for sharing across threads; Rc is its lighter, single-threaded version.
Best practice
Reach for Rc<T> only when you genuinely need multiple owners of the same data and can't determine a single clear owner, typically in tree or graph structures where nodes are shared. Most code doesn't need it: a single owner with borrows (&T) is simpler, cheaper, and checked at compile time. Rc trades a little runtime cost (the counting) and gives up compile-time single-ownership clarity in exchange for shared ownership. Use it when sharing is the actual requirement, not by default.
Quiz time
Question #1
What does Rc::clone do, and how does it differ from a normal .clone() that deep-copies?
Show solution
Rc::clone increments the reference count and returns another handle to the same underlying value, it copies only a pointer and bumps a counter, not the data, so it's cheap regardless of the value's size. A normal deep .clone() (on, say, a Vec) duplicates the whole value. The two are written differently by convention (Rc::clone(&x) vs x.clone()) specifically to flag the Rc version as cheap shared-ownership rather than an expensive copy.
Question #2
When is the value inside an Rc<T> actually freed?
Show solution
When the reference count reaches zero, that is, when the last Rc handle pointing to it is dropped. Each Rc's drop decrements the count; the drop that takes it to 0 frees the data. As long as any owner remains, the value stays alive. This is how shared ownership cleans up: no single owner decides, the count does, automatically and exactly once.
Question #3
Name the two main limitations of Rc<T>.
Show solution
(1) It provides shared immutable access only, you can't mutate through an Rc (multiple owners would mean multiple writers, breaking chapter 9's rules); for shared mutability you combine it with RefCell (Rc<RefCell<T>>, next lesson). (2) It's single-threaded only, its non-atomic counter isn't safe across threads, so the compiler forbids sending an Rc between threads; Arc<T> (chapter 22) is the thread-safe version.
Rc gives many owners but only shared, read-only access. To mutate shared data, you need to bend chapter 9's borrowing rules, safely. The next lesson (21.6) introduces RefCell<T> and interior mutability, moving the borrow check from compile time to run time, and finally explaining the term left dangling back in lesson 5.1.