Concurrency
Chapter 4 closed with a forward pointer to Arc<T> for thread-safety. This chapter is where that payoff lands: the same ownership and borrowing rules that prevented aliasing bugs in single-threaded code (Course 1, Chapter 4) also prevent data races across threads — enforced at compile time, not discovered at runtime.
Spawning Threads
thread::spawn returns a JoinHandle; .join() blocks until that thread completes.
The move Keyword and Ownership Across Threads
A spawned thread might genuinely outlive the function that spawned it — the compiler can't assume it won't. A closure that only borrows data risks that data being dropped before the thread finishes using it, so thread::spawn typically requires a move closure, forcing ownership of captured variables into the thread itself:
This is Course 1's move semantics (Chapter 3) and borrow-checker lifetime reasoning (Chapter 4), both directly enforced here — applied to a genuinely new context where a thread's actual lifetime is unpredictable.
Sharing Data Safely with Arc<Mutex<T>>
Chapter 4's Rc<RefCell<T>> pattern has a thread-safe sibling: Arc<T> ("Atomic Reference Counted") replaces Rc, and Mutex<T> replaces RefCell — using a real OS-level lock, where .lock() returns a guard that unlocks automatically when dropped:
mpsc Channels: Message Passing
std::sync::mpsc::channel() ("multiple producer, single consumer") returns a (Sender, Receiver) pair — threads communicate by sending owned values rather than sharing memory directly:
This is a direct expression of a proverb closely associated with Go's own community: "Do not communicate by sharing memory; instead, share memory by communicating." Rust and Go arrive at genuinely similar channel-based thinking here.
Contrast With Go's Goroutines & Channels (go2-3)
Go's goroutines are lightweight, green-thread-style tasks managed by Go's own runtime scheduler — many goroutines multiplex onto relatively few real OS threads. Rust's thread::spawn creates real OS threads directly — heavier per-thread, with no separate runtime scheduler required (Rust's own lightweight async tasks are Course 3's territory, a different mechanism entirely). Go's channels are first-class language syntax (ch <- value, <-ch); Rust's mpsc channels are an ordinary standard-library type, no special syntax needed.
The more important contrast: Go's concurrency safety net is best-effort — its race detector catches some data races, opt-in, typically during testing. Rust's guarantee is enforced by the compiler itself, for every concurrent program, as a hard requirement just to compile at all.
| Go | Rust | |
|---|---|---|
| Thread model | Lightweight goroutines, runtime-scheduled | Real OS threads (async tasks are separate, Course 3) |
| Channels | First-class language syntax | A standard-library type (mpsc) |
| Data-race safety | Best-effort — race detector, opt-in/testing-time | Compiler-enforced — required to compile at all |
thread::spawn / join
Creates a real OS thread; join() blocks until it finishes.
move Closures
Forces ownership transfer into the thread — required since a thread's lifetime is unpredictable.
Arc<Mutex<T>>
The thread-safe sibling of Rc<RefCell<T>> — shared, lockable mutable state.
mpsc Channels
Send owned values between threads instead of sharing memory directly.
Send (safe to move to another thread) and Sync (safe to share a reference across threads), are what the compiler actually checks. Trying to share a plain Rc<T> — not thread-safe, per Chapter 4's warning — across threads is a compile error, not a data race waiting to happen at runtime. This is the concrete mechanism behind Rust's "fearless concurrency" claim: it isn't a slogan, it's Send/Sync doing real, checked work.
Coding Challenges
Write code that spawns a thread using a move closure to print a Vec
Using Arc
Explain why Rust's compile-time thread safety is a stronger guarantee than Go's race detector, but is still not a complete guarantee of concurrency correctness — referencing deadlocks specifically as an example of what it doesn't cover.
📄 View solutionChapter 5 Quick Reference
- thread::spawn(|| {...}) — creates a real OS thread; returns a JoinHandle; .join() waits for completion
- move closures — required to transfer ownership into a thread, since its lifetime is unpredictable
- Arc<T> — thread-safe Rc; Mutex<T> — thread-safe RefCell, using a real OS lock
- Arc<Mutex<T>> — the multi-threaded analog of Rc<RefCell<T>>
- mpsc::channel() — a (Sender, Receiver) pair; send owned values instead of sharing memory
- Send/Sync — the marker traits the compiler checks to reject unsafe cross-thread sharing at compile time
- Go's race safety is best-effort tooling; Rust's is a hard, compiler-enforced compile requirement
- Mutex<T> prevents data races, not deadlocks — a real, uncaught category of concurrency bug remains possible
- Next chapter: Modules & Crates — the module system, Cargo.toml, workspaces, and publishing to crates.io