Threads

Scoped Threads

Scoped threads are OS threads whose execution lifetime is statically bounded by a lexical scope ('scope). Because the runtime guarantees that every thread spawned inside the scope finishes and joins before the scope exits, scoped threads can safely borrow non-'static local variables (&'env T and disjoint &'env mut T) directly from the parent thread's stack frame without Arc<T> or cloning!

Why Unscoped thread::spawn Cannot Borrow the Stack, While Scoped Threads Can

1. Unscoped `thread::spawn` (Requires `'static`):
   Parent Function (`'env`)
     |  let data = vec![1, 2, 3];
     |  thread::spawn(|| read(&data));  <-- Rejected! Child thread could still be running
     v                                      after Parent Function returns and drops `data`!

2. Scoped `thread::scope` (Bounded by `'scope` inside `'env`):
   Parent Function (`'env`)
     |  let data = vec![1, 2, 3];
     |  thread::scope(|s| {             <-- Scope `'scope` begins (`'env: 'scope`)
     |      s.spawn(|| read(&data));    <-- Safe! Borrows `&'env data`
     |      s.spawn(|| read(&data));
     |  });                             <-- BLOCKS here until ALL spawned threads exit!
     |  // `data` is guaranteed to still be alive and now completely unborrowed again!
     v

Comparing Unscoped Threads (thread::spawn) and Scoped Threads (thread::scope)

Property std::thread::spawn std::thread::scope (s.spawn)
Lifetime bound on closure F: 'static F: 'scope (can borrow local stack variables of lifetime 'env where 'env: 'scope)
Can borrow &Local across threads? No (must move or wrap in Arc<Local>) Yes (any number of threads can borrow &Local simultaneously!)
Can borrow disjoint &mut Local slices? No Yes (each thread can mutate a disjoint chunks_mut slice in place!)
What happens if JoinHandle is not explicitly .join()ed? Thread detaches and keeps running in background Scope automatically joins all remaining threads before thread::scope returns!
What happens if a worker thread panics and is not manually .join()ed? Panic is silently dropped with the detached handle thread::scope propagates the panic to the parent thread after all sibling threads finish!

Parallel Read-Plus-Disjoint-Write Without Arc or Mutex

Because the borrow checker tracks shared and mutable borrows across thread::scope, multiple worker threads can simultaneously read a shared stack lookup table (&table) while writing their outputs into disjoint mutable slices (&mut output[..]):

use std::thread;

fn main() {
    let multipliers = [2_u64, 3, 5, 7];
    let mut outputs = [0_u64; 4];

    thread::scope(|s| {
        // Borrow `multipliers` immutably (`&[u64; 4]`) across all 4 threads,
        // and borrow each element of `outputs` mutably (`&mut u64`) in 1 thread:
        for (idx, out_slot) in outputs.iter_mut().enumerate() {
            let mult_ref = &multipliers;
            s.spawn(move || {
                *out_slot = mult_ref[idx] * 100 + (idx as u64);
            });
        }
    }); // All 4 threads are automatically joined here before `outputs` is read below!

    println!("outputs: {:?}", outputs);
}
outputs: [200, 301, 502, 703]