In this lesson, you will learn how to launch OS-level threads and master the techniques required to move data into those threads while satisfying the Rust compiler’s strict lifetime requirements.
The Thread-Closure Conflict
When you use std::thread::spawn, you pass a closure that the new thread will execute. Because the operating system might keep that thread running long after the function that spawned it has returned, Rust requires the closure to have a 'static lifetime. It cannot hold references to local variables on the stack, as those variables could be destroyed before the thread finishes executing.
If you try this, the compiler will stop you:
let data = vec![1, 2, 3];
// This fails because 'data' only lives as long as the current scope
std::thread::spawn(|| {
println!("{:?}", data);
});
Resolving Lifetimes with move
To solve this, we use the move keyword. By prefixing the closure with move, we force the closure to take ownership of the variables it uses. Instead of referencing the stack memory of the parent function, the thread receives its own owned copy of the data.
let data = vec![1, 2, 3];
let handle = std::thread::spawn(move || {
// 'data' is now owned by the thread closure
println!("{:?}", data);
});
handle.join().unwrap();
By moving the data, the lifetime of the data vector effectively becomes the lifetime of the thread itself. The compiler is satisfied because the data is guaranteed to exist for as long as the closure exists.
When to Use This
This pattern is ideal for tasks that require isolated, long-running background work. However, keep in mind that move performs a shallow copy or a move of ownership. If your data is large, you aren't creating a bottleneck in the parent thread by holding onto the original variable—it becomes inaccessible in the outer scope once moved. If you need to share access rather than transfer ownership, you would look toward shared synchronization primitives (which we will encounter in future modules), but for standalone tasks, the move keyword is your primary tool for bridging the gap between stack-based local variables and thread-based execution.
Key Takeaway: The move keyword allows you to safely transfer ownership of local variables into a thread, satisfying the 'static requirement by ensuring the thread owns its own data.