Step 1 of 7
Spawning a thread and joining it
Every program so far has done one thing at a time, top to bottom. A thread is an independent line of execution inside your program: it has its own place in the code and its own local variables, and it runs alongside the others. Every program starts with one, the main thread. std::thread::spawn starts another.
Two words you'll hear a lot:
- Concurrency: several tasks are in progress at the same time (they take turns, or overlap).
- Parallelism: several tasks literally run at the same instant, on different CPU cores.
Threads give you both: the operating system runs them on as many cores as it has. Teams use threads to put every core to work on heavy jobs (resizing images, crunching log files, compressing backups), or to keep one part of a program responsive while another part waits.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
let mut total: u64 = 0;
for n in 1..=1_000 {
total += n;
}
total
});
println!("main thread keeps going");
let total = handle.join().unwrap();
println!("the worker computed {total}");
}
main thread keeps going
the worker computed 500500
Piece by piece:
use std::thread;brings thethreadmodule into scope so you can writethread::spawn.thread::spawntakes a closure (module 26): the code the new thread runs.|| { ... }is a closure with no parameters. Its last expression,total, is what the thread returns.spawnreturns immediately, without waiting, and gives you aJoinHandle<T>, whereTis the closure's return type (u64here). The worker starts running on its own, whilemaincarries on and prints its line.handle.join()waits until that thread has finished, then gives back its return value wrapped in aResult:Ok(value)normally,Err(...)if the thread panicked (step 6)..unwrap()is the usual choice: if a worker crashed,mainshould crash too.
Threads run in an unpredictable order
If two threads both print, you can't know which line comes first. The operating system's scheduler decides which thread runs on which core, and when, and the answer changes from run to run:
let handle = thread::spawn(|| println!("hello from the worker"));
println!("hello from main");
handle.join().unwrap();
// Sometimes "main" prints first, sometimes "worker". Both are correct.
That's why the examples and exercises in this module never print from inside a worker. Workers return their results, and the main thread prints them after join, in a fixed order. Tests at work are written the same way: output that changes between runs makes a flaky test.
When main ends, the program ends
When main returns, the whole process exits, and every thread still running is stopped mid-work. A worker you forgot to join may never finish (or never even start). join is how you wait for it.
Common mistakes
- Forgetting to join. The program ends before the worker's work is done, and its result is silently lost.
- Joining right after spawning.
thread::spawn(a).join(); thread::spawn(b).join();runsa, waits, then runsb: no parallelism at all. Spawn every thread first, then join them. - Treating
join()as the value. It returns aResult, solet total: u64 = handle.join();fails witherror[E0308]: mismatched types. Add.unwrap().
Your task
Fill in the blanks: start a thread with thread::spawn, then wait for it and take its result with join. The program should print word count: 10.
Challenge 2
A JoinHandle remembers the type its thread returns. Fill in that type in the annotation so the program compiles and prints max temperature: 31.
Challenge 3
Spawn two threads before joining either of them, so they can run at the same time:
- the first adds up the even numbers from 1 to 100,
- the second counts how many numbers from 1 to 100 are multiples of 7.
Then join both and print:
even sum: 2550
multiples of 7: 14