Threads
Moving Data into Threads
When spawning an unconstrained OS thread with std::thread::spawn, any data from the parent stack frame that the child thread uses must be moved by value into the thread's closure so the child thread owns its data for 'static.
Why Borrowing Local Stack Variables in thread::spawn Fails (E0373)
By default, closures capture variables by shared reference (&T) when they only read them. However, a thread spawned with std::thread::spawn can continue running long after the parent function returns and deallocates its stack frame. Therefore, thread::spawn requires F: 'static and rejects any closure that borrows a local variable (E0373):
// ❌ does not compile
use std::thread;
fn spawn_printer() {
let items = vec![10, 20, 30];
// Without `move`, the closure captures `&items`, which does not live for `'static`:
let handle = thread::spawn(|| {
println!("items: {:?}", items);
});
handle.join().unwrap();
}
fn main() {
spawn_printer();
}
error[E0373]: closure may outlive the current function, but it borrows `items`, which is owned by the current function
--> src/main.rs:8:32
|
8 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value `items`
9 | println!("items: {:?}", items);
| ----- `items` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/main.rs:8:18
|
8 | let handle = thread::spawn(|| {
| __________________^
9 | | println!("items: {:?}", items);
10| | });
| |______^
help: to force the closure to take ownership of `items` (and any other referenced variables), use the `move` keyword
|
8 | let handle = thread::spawn(move || {
| ++++
Three Ways to Supply Data to a Spawned Thread
| Strategy | When to Use | Mechanism |
|---|---|---|
1. Move ownership (move ||) |
Only the child thread needs the data (or it returns the data back via .join()) |
Transfers ownership of Vec<T>, String, etc. into the child thread |
2. Clone or Arc::clone before move || |
Both parent and child threads need the data across unbounded lifetimes | let shared = Arc::clone(&data); thread::spawn(move || ...) |
| 3. Partition into owned chunks | Splitting a Vec<T> across $N$ worker threads |
Drain or split the vector into $N$ owned Vec<T> batches and move 1 batch per thread |
use std::thread;
fn main() {
let mut dataset: Vec<u32> = (1..=6).collect();
// Split off the second half as a separate owned `Vec<u32>`:
let right_half: Vec<u32> = dataset.split_off(3);
let left_half: Vec<u32> = dataset;
// Move `left_half` into Thread 1 and `right_half` into Thread 2:
let h1 = thread::spawn(move || left_half.into_iter().sum::<u32>());
let h2 = thread::spawn(move || right_half.into_iter().sum::<u32>());
let total = h1.join().unwrap() + h2.join().unwrap();
println!("partitioned sum: {}", total);
}
partitioned sum: 21