---
title: "21.x Chapter 21 summary and quiz"
description: "Review of Box, Deref, Drop, Rc, RefCell, and Weak, the smart pointers and traits that let you reach for the heap and share ownership deliberately."
url: "https://learnrust.net/chapter-21/chapter-21-summary-and-quiz/"
last_updated: "2026-06-13"
---

# 21.x Chapter 21 summary and quiz

This chapter named a pattern you'd been using all along and added three deliberate tools to it. Review, then a capstone that combines them.

## Quick review

A **smart pointer** is a value that owns data and adds behavior, you've used `String` and `Vec` as smart pointers since chapter 1 ([21.1](https://learnrust.net/chapter-21/introduction-to-smart-pointers/)). Two traits make one: `Deref` (reach through it like a pointer) and `Drop` (clean up when it dies).

**`Box<T>`** puts a single value on the heap with one owner ([21.2](https://learnrust.net/chapter-21/box/)). Its essential use is recursive types: `enum List { Cons(i32, Box<List>), Nil }` compiles where the un-boxed version has "infinite size," because a `Box` is a fixed-size pointer. Also used for trait objects and large moves.

**`Deref`** defines `*` for a type, returning a reference to its inner value ([21.3](https://learnrust.net/chapter-21/deref-and-deref-coercion/)). **Deref coercion** auto-inserts `deref` calls so `&Box<String>` works where `&str` is wanted, which is exactly why `&String` works as `&str` (the chapter-9 mystery, solved).

**`Drop`** runs cleanup code when a value goes out of scope ([21.4](https://learnrust.net/chapter-21/drop-in-depth/)); values drop in reverse declaration order; drop early with `std::mem::drop(v)` (never `v.drop()`). This is **RAII**: resource lifetime tied to value lifetime, the reason Rust needs neither manual frees nor a garbage collector.

**`Rc<T>`** gives shared ownership by reference counting ([21.5](https://learnrust.net/chapter-21/rc/)): `Rc::clone` bumps a count cheaply (no deep copy), and the value frees when the last owner drops. Shared but immutable, and single-threaded only (`Arc` for threads, chapter 22).

**`RefCell<T>`** moves the borrow check to run time, enabling **interior mutability**, mutation through a shared reference via `borrow()`/`borrow_mut()`, panicking (`BorrowMutError`) on violations instead of failing to compile ([21.6](https://learnrust.net/chapter-21/refcell-and-interior-mutability/)). `Rc<RefCell<T>>` combines shared ownership with shared mutability.

**Reference cycles** of strong `Rc`s leak memory (a *safe* bug, wasteful but not corrupting); **`Weak<T>`** breaks them with non-owning references read via `upgrade()` ([21.7](https://learnrust.net/chapter-21/reference-cycles-and-weak/)).

## Quiz time

**Question #1**

Why must a recursive type like a cons list put its recursive field behind a `Box` (or `Rc`)?

<details class="solution">
<summary>Show solution</summary>

Without indirection, computing the type's size requires knowing its own size (it contains itself), giving "infinite size" (E0072), so the compiler can't lay it out on the stack. A `Box` (or `Rc`) is a fixed-size pointer regardless of what it points to, so wrapping the recursive field makes the type a known, finite size. The compiler's error message even suggests inserting a `Box`, `Rc`, or `&`.

</details>

**Question #2**

`Rc<T>` and `RefCell<T>` each give up something that `&`/`&mut` provide. What, and why combine them?

<details class="solution">
<summary>Show solution</summary>

`Rc<T>` allows multiple owners but only shared (immutable) access. `RefCell<T>` allows mutation through a shared reference but only single ownership, and it checks the borrow rules at runtime (panicking on violation) rather than compile time. Combining them as `Rc<RefCell<T>>` gives data that is both shared (many owners, via `Rc`) and mutable (via `RefCell`), which neither provides alone.

</details>

**Question #3**

Predict the output:

```rust
struct Loud(i32);
impl Drop for Loud {
    fn drop(&mut self) { println!("drop {}", self.0); }
}
fn main() {
    let a = Loud(1);
    let b = Loud(2);
    drop(a);
    println!("end of main");
}
```

<details class="solution">
<summary>Show solution</summary>

```
drop 1
end of main
drop 2
```

`drop(a)` (the `std::mem::drop` function) ends `a`'s life immediately, printing `drop 1`. Then `end of main` prints. At the closing brace, `b` is dropped (`a` already is), printing `drop 2`. Note `b` drops at scope end in reverse-declaration order, but `a` was dropped early by hand, so only `b` remains for the automatic cleanup.

</details>

**Question #4**

A tree node holds `Rc` pointers to its children and you want each child to also reference its parent. Why should the parent-pointer be a `Weak`, not an `Rc`?

<details class="solution">
<summary>Show solution</summary>

If both directions were strong `Rc`s, parent and child would each keep the other's count above zero, a reference cycle that leaks: neither is ever freed. Making the child-to-parent link a `Weak` means it doesn't contribute to the strong count, so it doesn't keep the parent alive, breaking the cycle. Ownership flows downward (parent owns children, strong); the upward back-reference is non-owning (weak), read safely via `upgrade()`.

</details>

## Chapter capstone

Build a small shared, mutable counter that several handles can increment, then print the final value. This exercises `Rc` (shared ownership) and `RefCell` (interior mutability) together, the combination you'll meet constantly.

Write a program that creates a counter starting at 0, makes three handles to it, increments through each handle a different number of times (1, 2, and 3), and prints the total.

<details class="solution">
<summary>Show solution</summary>

```rust
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let counter = Rc::new(RefCell::new(0));

    let h1 = Rc::clone(&counter);
    let h2 = Rc::clone(&counter);
    let h3 = Rc::clone(&counter);

    *h1.borrow_mut() += 1;                 // +1
    for _ in 0..2 { *h2.borrow_mut() += 1; }   // +2
    for _ in 0..3 { *h3.borrow_mut() += 1; }   // +3

    println!("count: {}", counter.borrow());
    println!("owners: {}", Rc::strong_count(&counter));
}
```

```
count: 6
owners: 4
```

`Rc::new(RefCell::new(0))` is the shared-mutable pattern: the `Rc` layer gives multiple owners, the `RefCell` layer lets any owner mutate the value. Each `Rc::clone` is a cheap new handle to the *same* counter, so all increments land on one value (total 6). The owner count is 4: the original `counter` plus the three handles, none dropped yet. Crucially, the `RefCell` borrows in each `*h.borrow_mut() += 1` don't overlap (each ends at its semicolon), so no `BorrowMutError`, holding two `borrow_mut()`s across the same moment would panic. This is single-threaded shared mutable state; chapter 22 does the same job *across threads* with `Arc<Mutex<T>>`, where the borrow tracking becomes locking.

</details>

You can now allocate on the heap deliberately, share ownership, mutate shared data, and clean up automatically, the full memory toolkit for single-threaded programs. Chapter 22 takes the leap that no other beginner course makes this gently: running code on multiple threads at once, where `Rc` becomes `Arc`, `RefCell` becomes `Mutex`, and the data race the borrow checker warned about back in chapter 9 becomes the central enemy that Rust, uniquely, makes impossible to compile.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
