Threads

thread.scope

std::thread::scope (stabilized in Rust 1.63) is the standard library function that creates a scoped thread environment &'scope Scope<'scope, 'env>, allowing threads spawned via s.spawn(...) to borrow local variables of lifetime 'env and returning the value produced by the scope body after all spawned threads have terminated.

Dual-Lifetime Signature of std::thread::scope

pub fn scope<'env, F, T>(f: F) -> T
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,

Understanding the two lifetimes 'env and 'scope explains how thread::scope achieves compile-time safety without garbage collection:

Lifetime Hierarchy of `std::thread::scope`:

  Outer Environment Lifetime `'env` (Variables declared BEFORE `thread::scope`)
  +-------------------------------------------------------------------------+
  | let stack_vec = vec![10, 20, 30];                                       |
  |                                                                         |
  |   Inner Scope Lifetime `'scope` (Duration of `thread::scope(|s| ...)`)  |
  |   +-----------------------------------------------------------------+   |
  |   | s.spawn(|| { /* can borrow `&'env stack_vec` AND `&'scope s` */ })  |
  |   | // Spawned threads can even spawn nested sibling threads via `s`!|   |
  |   +-----------------------------------------------------------------+   |
  |   // All `'scope` threads are guaranteed joined before `'scope` ends!   |
  +-------------------------------------------------------------------------+
Lifetime What It Represents Constraint
'env Lifetime of stack variables declared outside thread::scope 'env: 'scope ('env outlives the entire thread::scope call)
'scope Higher-ranked (for<'scope>) lifetime of the thread::scope body itself Ends before thread::scope returns; threads can borrow 'env data AND &'scope Scope

Returning Computed Values Directly from thread::scope

Because thread::scope returns whatever value T its closure produces (after joining any unjoined scoped threads), you can collect ScopedJoinHandle<'scope, R> results inside the scope and return the aggregated result directly:

use std::thread;

fn parallel_map<In, Out, F>(items: &[In], f: F) -> Vec<Out>
where
    In: Sync,
    Out: Send,
    F: Fn(&In) -> Out + Sync,
{
    thread::scope(|s| {
        let handles: Vec<thread::ScopedJoinHandle<'_, Out>> = items
            .iter()
            .map(|item| s.spawn(|| f(item)))
            .collect();

        handles
            .into_iter()
            .map(|h| h.join().unwrap())
            .collect()
    })
}

fn main() {
    let words = [String::from("rust"), String::from("scoped"), String::from("threads")];
    let lengths = parallel_map(&words, |w| (w.as_str(), w.len()));

    // Notice that `lengths` can even contain `&str` slices borrowing from `words`!
    println!("mapped slices & lengths: {:?}", lengths);
}
mapped slices & lengths: [("rust", 4), ("scoped", 6), ("threads", 7)]

Spawning Sibling Threads Recursively via &'scope Scope

Because s is passed into thread::scope as a shared reference &'scope Scope<'scope, 'env> (and Scope implements Sync), a thread spawned inside s.spawn(...) can capture s by copy (let s = s;) and call s.spawn(...) from inside the worker thread to dynamically spawn recursive divide-and-conquer tasks (such as parallel quicksort or tree traversal) within the same scope!

use std::thread;

fn parallel_sum<'env>(slice: &'env [u64]) -> u64 {
    thread::scope(|s| {
        fn recurse<'scope, 'env: 'scope>(
            s: &'scope thread::Scope<'scope, 'env>,
            sub: &'env [u64],
        ) -> u64 {
            if sub.len() <= 2 {
                return sub.iter().sum();
            }
            let (left, right) = sub.split_at(sub.len() / 2);
            // Spawn left half on a sibling scoped thread via `s`, compute right half on current thread:
            let left_handle = s.spawn(move || recurse(s, left));
            let right_sum = recurse(s, right);
            left_handle.join().unwrap() + right_sum
        }

        recurse(s, slice)
    })
}

fn main() {
    let data = [10_u64, 20, 30, 40, 50, 60, 70, 80];
    println!("recursive scoped sum: {}", parallel_sum(&data));
}
recursive scoped sum: 360