RustArena

Threads in Rust: thread::spawn, join and move closures

How to run code in parallel with std::thread in Rust: spawn and join, returning values, move closures, scoped threads, and the E0373 error.

A thread runs code at the same time as the rest of your program. std::thread::spawn starts one and returns a JoinHandle; calling .join() waits for the thread to finish and gives back the value its closure returned.

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        let mut sum = 0u64;
        for i in 1..=1_000 {
            sum += i;
        }
        sum
    });
    let total = handle.join().unwrap();
    println!("sum is {total}");
}
sum is 500500

join() returns a Result: Err if the thread panicked. A panic in a spawned thread doesn't crash the main thread; it's reported when you join.

Giving data to a thread: move

A spawned thread may run longer than the function that started it, so its closure can't borrow local variables:

use std::thread;

fn main() {
    let names = vec!["Ferris", "Corro"];
    let handle = thread::spawn(|| {
        println!("{} names", names.len());
    });
    handle.join().unwrap();
}
error[E0373]: closure may outlive the current function, but it borrows `names`, which is owned by the current function
 --> src/main.rs:5:32
  |
5 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `names`
6 |         println!("{} names", names.len());
  |                              ----- `names` is borrowed here
  |
help: to force the closure to take ownership of `names` (and any other referenced variables), use the `move` keyword

Add move and the closure owns names. The thread can then use it as long as it likes, and main can't use it afterwards. If both need the data, clone it before spawning, or share it with Arc.

Scoped threads: borrowing safely

thread::scope guarantees that every thread spawned inside it finishes before the scope returns, so those threads may borrow local data, no move or Arc needed:

thread::scope(|s| {
    for chunk in data.chunks(1000) {
        s.spawn(move || process(chunk));
    }
}); // all threads have finished here

This is the simplest way to split work over a slice.

Many threads, results in order

Spawn the threads, keep their handles in a Vec, then join them in order. Collecting results this way gives deterministic output even though the threads finish in any order. Printing from inside the threads doesn't: the lines interleave differently on every run.

Shared state and communication

Common mistakes

Example

use std::thread;

fn count_primes(from: u32, to: u32) -> usize {
    (from..to).filter(|&n| n >= 2 && (2..).take_while(|d| d * d <= n).all(|d| n % d != 0)).count()
}

fn main() {
    // Each thread owns its range (move) and returns a value.
    let ranges = [(0, 25_000), (25_000, 50_000), (50_000, 75_000), (75_000, 100_000)];
    let mut handles = Vec::new();
    for (from, to) in ranges {
        handles.push(thread::spawn(move || (from, to, count_primes(from, to))));
    }
    let mut total = 0;
    for h in handles {
        let (from, to, n) = h.join().expect("worker panicked");
        println!("{from:>6}..{to:<6} {n} primes");
        total += n;
    }
    println!("total {total}");

    // Scoped threads can borrow local data.
    let words = vec!["ferris", "crab", "rustacean", "borrow"];
    let lengths: Vec<usize> = thread::scope(|s| {
        let handles: Vec<_> = words.iter().map(|w| s.spawn(move || w.len())).collect();
        handles.into_iter().map(|h| h.join().unwrap()).collect()
    });
    println!("{words:?} -> {lengths:?}");

    // A panic in a thread is reported by join, not by crashing main.
    let result = thread::spawn(|| {
        let empty: Vec<i32> = Vec::new();
        empty[0] // panics: index out of bounds
    })
    .join();
    println!("worker panicked: {}", result.is_err());
}

Output:

     0..25000  2762 primes
 25000..50000  2371 primes
 50000..75000  2260 primes
 75000..100000 2199 primes
total 9592
["ferris", "crab", "rustacean", "borrow"] -> [6, 4, 9, 6]
worker panicked: true

Practice it