In this lesson, you will learn how to safely borrow local variables inside threads without forcing them to live forever.
The Problem with Native Threads
When you use std::thread::spawn, the compiler requires the closure to have a 'static lifetime. This is because the Rust compiler cannot guarantee that the spawned thread will finish before the main function exits. If the thread outlived the data it was borrowing from your stack, you would end up with a dangling pointer and a crash.
Previously, we worked around this by using Arc to move heap-allocated memory into threads. But what if you have a local array on your stack that you just want to process in parallel for a few milliseconds? Cloning data into Arc just to satisfy the compiler is expensive and unnecessary.
The Scoped Thread Solution
Rust’s std::thread::scope allows you to create a "scope" where threads are guaranteed to finish before the scope ends. Because the compiler now has a strict proof that these threads cannot outlive the parent function, it relaxes the 'static requirement. You can now borrow variables from the stack directly.
let numbers = vec![1, 2, 3];
std::thread::scope(|s| {
// We can borrow 'numbers' because the compiler
// guarantees these threads join before scope ends.
s.spawn(|| {
println!("The first element is: {}", numbers[0]);
});
s.spawn(|| {
println!("The length is: {}", numbers.len());
});
});
// The threads are automatically joined here.
// It is safe for 'numbers' to go out of scope now.
By using std::thread::scope, you inform the compiler that your threads are strictly bounded by the lifetime of the local stack frame. This removes the need for expensive reference counting or heap allocations, allowing you to perform concurrent tasks on local data with zero overhead and full safety.
Key Takeaway: Scoped threads allow you to borrow local stack data safely by guaranteeing that all spawned threads complete before the scope exits, effectively lifting the 'static lifetime requirement.