---
title: "22.x Chapter 22 summary and quiz"
description: "Review of threads, channels, Mutex and Arc, the Send and Sync marker traits, and rayon, with a capstone parallel computation."
url: "https://learnrust.net/chapter-22/chapter-22-summary-and-quiz/"
last_updated: "2026-06-13"
---

# 22.x Chapter 22 summary and quiz

This chapter did what almost no beginner course attempts: real concurrency, made safe by the ownership rules you already knew. Review, then a capstone.

## Quick review

A **thread** is an independent line of execution; `thread::spawn(closure)` starts one running concurrently ([22.1](https://learnrust.net/chapter-22/threads/)). It returns a `JoinHandle` whose `.join()` waits for the thread to finish; without joining, `main` returning kills spawned threads. Spawned closures usually need `move`, so the thread owns its data and nothing dangles, the chapter-8/9 rules across a thread boundary.

**Channels** pass messages between threads ([22.2](https://learnrust.net/chapter-22/message-passing-channels/)): `mpsc::channel()` gives a transmitter (`tx.send`) and receiver (`rx.recv` or iteration). `send` *moves* ownership, so a sent value can't be used by the sender again, which makes data races impossible by construction. Clone `tx` for multiple producers; the receiver-iterator ends when all transmitters drop.

**Shared state** uses `Mutex` plus `Arc` ([22.3](https://learnrust.net/chapter-22/shared-state-mutex-and-arc/)): `Mutex<T>` gives one-thread-at-a-time access via `.lock()` (the lock releases automatically on the guard's drop), and `Arc<T>` gives thread-safe shared ownership. `Arc<Mutex<T>>` is the across-threads version of chapter 21's `Rc<RefCell<T>>`. Rust prevents data races but not **deadlock**.

**`Send`** (safe to move to another thread) and **`Sync`** (safe to share by reference) are marker traits the compiler derives automatically from a type's fields ([22.4](https://learnrust.net/chapter-22/send-and-sync/)). `Rc` isn't `Send` and `RefCell` isn't `Sync`, which is *why* you swap them for `Arc` and `Mutex` across threads. These traits are what make "fearless" a compile-time guarantee.

**`rayon`** parallelizes iterator pipelines by changing `iter()` to `par_iter()` ([22.5](https://learnrust.net/chapter-22/data-parallelism-with-rayon/)), safe because it builds on `Send`/`Sync`. Worth it for many items of CPU-bound work; not for small or I/O-bound jobs.

## Quiz time

**Question #1**

Why does this fail to compile, and what's the fix?

```rust
let v = vec![1, 2, 3];
let handle = std::thread::spawn(|| println!("{v:?}"));
handle.join().unwrap();
```

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

The closure *borrows* `v`, but the spawned thread might outlive `main`, so the borrow could dangle (E0373). The compiler rejects it. Fix: add `move`, `spawn(move || println!("{v:?}"))`, so the closure takes ownership of `v` and the thread keeps it alive as long as it runs.

</details>

**Question #2**

In a channel, why can't the sender use a value after `tx.send(value)`?

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

`send` takes ownership and moves the value into the channel (chapter 8), so the sender no longer owns it; using it afterward is a use-after-move error (E0382). This guarantees the value isn't accessible to both sender and receiver simultaneously, eliminating that data race. Channels are safe because ownership *transfers* rather than being shared.

</details>

**Question #3**

What's the relationship between `Arc<Mutex<T>>` and `Rc<RefCell<T>>`, and why can't you just use `Rc<RefCell<T>>` across threads?

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

They're the same pattern, shared ownership of mutable data, with `Arc<Mutex<T>>` being the thread-safe version: `Arc` (atomic count) replaces `Rc` (non-atomic), and `Mutex` (locking) replaces `RefCell` (runtime borrow flag). You can't use `Rc<RefCell<T>>` across threads because `Rc` isn't `Send` and `RefCell` isn't `Sync` (their bookkeeping isn't safe under concurrent access), so the compiler refuses to send them between threads (lesson 22.4).

</details>

**Question #4**

Rust prevents data races at compile time. Name a concurrency bug it does *not* prevent.

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

Deadlock, two threads each holding a lock the other needs, waiting forever (also livelock, and general logic errors in ordering). These aren't memory-safety violations, so the compiler allows them; avoiding them takes discipline (consistent lock ordering, short critical sections, preferring message passing). "Fearless concurrency" means no data races, not no concurrency bugs of any kind. (Reference-cycle leaks from chapter 21 are another safe-but-unwanted case.)

</details>

## Chapter capstone

Sum the squares of the numbers 1 through 1000 across multiple threads *by hand*, using `Arc<Mutex<T>>`, splitting the range among four threads. (In real code you'd use `rayon`; doing it manually cements the pattern.)

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

```rust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let total = Arc::new(Mutex::new(0u64));
    let mut handles = vec![];

    // Four threads, each handling a quarter of the range.
    for chunk in 0..4 {
        let total = Arc::clone(&total);
        let handle = thread::spawn(move || {
            let start = chunk * 250 + 1;
            let end = start + 250;
            let partial: u64 = (start..end).map(|n| n * n).sum();

            let mut t = total.lock().unwrap();
            *t += partial;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("sum of squares 1..=1000: {}", *total.lock().unwrap());
}
```

```
sum of squares 1..=1000: 333833500
```

Each thread computes the sum of squares for its own quarter of the range *locally* (no lock needed for that, it's the thread's own data), then locks the shared `total` just once to add its partial sum. `Arc::clone` gives each thread a handle to the same `Mutex`; the lock serializes the four `+=` updates so they don't race; `join` waits for all four. Note the design choice that makes this efficient: do the heavy work lock-free on local data, and hold the lock only for the tiny final combine. That's the same idea `rayon` applies automatically, and the same answer (333833500) the sequential `(1..=1000).map(|n| n*n).sum()` gives.

</details>

You can now run code on many threads, pass messages, share state safely, and parallelize with one word. That's concurrency for CPU-bound work, many threads doing computation. Chapter 23 turns to the other kind: *async*, the model for a different problem, thousands of tasks that spend most of their time *waiting* (for the network, for disk, for a timer), handled efficiently without a thread apiece. It's also where, as the course has promised, Rust stops being seamless and we tell you so honestly.

## Sitemap

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