Rust's Scoped Threads: Borrowing Across Threads Safely
Scoped threads let you borrow local variables from a parent thread without complex wrappers. The scope guarantees all spawned threads are joined before it exits, satisfying the borrow checker. Use it to parallelize work on stack data.
WHY IT EXISTS: Standard threads created with std::thread::spawn require any captured data to have a 'static lifetime. This prevents them from borrowing local variables from their parent's stack, forcing developers to use heap-allocated, reference-counted pointers like Arc. This adds complexity and overhead for simple fork-join patterns.
THE MENTAL MODEL: A scoped thread is like a supervised temporary worker. The std::thread::scope block acts as the supervisor, guaranteeing that all threads started within it will finish before the block exits. Because of this guarantee, the Rust compiler can safely allow these threads to borrow data from the parent thread's stack, knowing the data will not be dropped while the threads are still running.
HOW IT WORKS: You call thread::scope with a closure that receives a Scope object, s. You use s.spawn() to create new threads that can borrow variables from the outer function. When your closure finishes, the scope function automatically waits for (joins) all spawned threads that haven't been manually joined. This blocking join is the key to its memory safety, which the compiler enforces by ensuring the borrowed data's lifetime ('env) outlives the scope's lifetime ('scope).
WHEN TO USE IT: Use scoped threads for structured, fork-join parallelism. A classic case is splitting a mutable slice or a vector into chunks and processing each chunk in a separate thread. It's simpler and often more performant than using Arc<Mutex<T>> for data that only needs to be shared for a short, well-defined period.
WHEN NOT TO USE IT: Do not use scoped threads for long-running, "detached" background tasks that should outlive the function that spawned them. For those, the standard std::thread::spawn is appropriate, and you must use 'static data. Also, if a spawned thread panics, the entire scope call will panic. If you need to handle thread failures, you must manually join their handles within the scope and check the Result.
ONE CANONICAL EXAMPLE: Imagine you have a vector a and an integer x. Inside a thread::scope block, you can spawn one thread that reads from a and another that writes to x. Because the scope guarantees both threads finish before the block ends, the compiler allows this borrowing without Arc or Mutex. After the scope, x will hold its updated value, and you can continue to use a safely.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.