Build the mental model
Running an OS thread with `std::thread::spawn` requires passing it a closure — if that closure captures a variable from the main thread, the `move` keyword must be added explicitly, moving ownership entirely to the spawned thread; the main thread can no longer use that variable afterward, applying Lesson 6's ownership rule right across a thread boundary. The `Send` trait (safe to transfer ownership from one thread to another) and `Sync` trait (safe for multiple threads to access a reference concurrently) are automatically implemented for most types, but types like `Rc<T>` deliberately don't implement them, so trying to use `Rc<T>` across threads triggers an immediate compile error — this is Rust's signature guarantee that turns a data race, normally an intermittent and notoriously hard-to-debug runtime bug, into a compile-time error instead. `mpsc` (multiple producer, single consumer) channels are a pattern for threads to communicate by passing messages rather than sharing mutable state that needs locks — `tx.send(value)` moves ownership into the channel, and `rx.recv()` retrieves it on the other end — similar to Go's CSP philosophy of "communicate by sharing memory" done the opposite way, except Rust enforces the ownership move at compile time, so the sender can never use that data again once it's been sent through the channel.
Connect it to a real scenario
Previewing the concurrent batch processor (Lesson 23's project), you can split a batch of lesson files across multiple threads with `std::thread::spawn` for word counting — moving the file list into a `move` closure means the main thread can no longer reference that list afterward. To send each thread's word count result back to the main thread, create `mpsc::channel()` and clone `tx` for every thread — each thread sends its result with `tx.send(count)`, and the main thread accumulates them with `rx.iter()`, aggregating results without needing any shared `Mutex` state at all. Trying to share `Rc<Vec<String>>` (from Lesson 17) across threads produces an immediate compile error, so `Arc<Vec<String>>` (atomic reference count) must be used instead.
Try the working example
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
for batch in [vec!["a", "bb", "ccc"], vec!["dddd", "e"]] {
let tx = tx.clone();
thread::spawn(move || {
let count: usize = batch.iter().map(|w| w.len()).sum();
tx.send(count).unwrap();
});
}
drop(tx);
let total: usize = rx.iter().sum();
println!("total chars: {total}");
}You print "total chars: 11", showing two threads sent results through the channel and the main thread accumulated them.5-minute try-it
Spawn three threads with `thread::spawn` — have each send a numeric value (of your choosing) back to the main thread through an `mpsc::channel` — have the main thread sum the results with `rx.iter().sum()`.
One important caution
Trying to borrow an outer variable inside a `thread::spawn` closure without the `move` keyword — since the spawned thread might outlive the main thread, a borrowed reference could dangle, so the compiler blocks it with an immediate compile error.
Trying to share one `mpsc::Sender` (`tx`) across multiple threads without cloning it — each thread needs its own copy via `tx.clone()`, otherwise you run into ownership-move problems.