Step 2 of 7
move closures: giving data to a thread
The closures in step 1 only used data they created themselves. Usually a thread needs data from outside. Here's the obvious attempt:
use std::thread;
fn main() {
let scores = vec![10, 20, 30];
let handle = thread::spawn(|| scores.iter().sum::<i32>());
println!("{}", handle.join().unwrap());
}
error[E0373]: closure may outlive the current function, but it borrows `scores`, which is owned by the current function
help: to force the closure to take ownership of `scores` (and any other referenced variables), use the `move` keyword
Why does Rust refuse? A closure that only reads scores borrows it. But the compiler has no idea how long the new thread will run: you might never join it, and the function that owns scores could return and drop the vector while the thread is still reading it. That would be a dangling reference, exactly what the borrow checker exists to prevent. So thread::spawn demands a closure that borrows nothing from the function that spawned it. In Rust's terms the closure must be 'static: everything it holds is owned, or lives for the whole program (like string literals).
The fix is in the help line. Writing move before the closure moves every variable the closure uses into it, so the thread owns its data:
use std::thread;
fn main() {
let scores = vec![10, 20, 30];
let handle = thread::spawn(move || scores.iter().sum::<i32>());
println!("total: {}", handle.join().unwrap());
}
total: 60
The vector now lives inside the closure. When the thread finishes, the closure is dropped, and the vector with it.
After a move, main can't use it
move is a real move, just like passing a String to a function (module 9):
use std::thread;
fn main() {
let scores = vec![10, 20, 30];
let handle = thread::spawn(move || scores.len());
println!("{} scores: {:?}", handle.join().unwrap(), scores);
}
error[E0382]: borrow of moved value: `scores`
Three ways out, depending on what you need:
- Clone before spawning and move the clone:
let for_worker = scores.clone();thenmove || for_worker.len(). Both threads have their own copy. Fine for small data. - Get it back: the thread returns the data as (part of) its result, and
joinhands it back to you. - Borrow with scoped threads, which step 4 shows. No copy at all.
Option 2 looks like this:
use std::thread;
fn main() {
let orders = vec![120, 45, 300];
let handle = thread::spawn(move || {
let mut sorted = orders;
sorted.sort();
sorted
});
let sorted = handle.join().unwrap();
println!("{sorted:?}");
}
[45, 120, 300]
Copy types are copied
For Copy types (integers, f64, bool, char), move copies the value into the closure. The original stays usable, and changes made inside the thread don't affect it: the thread changed its own copy.
Common mistakes
- Cloning inside the closure:
thread::spawn(move || scores.clone().len())still movesscoresinto the closure first. Clone beforespawn, into a new variable. - Expecting
moveto share: aftermove, the data belongs to the thread. To share one piece of data between threads you needArc(step 5) or scoped threads (step 4). - Ignoring the help line: rustc's
help:lines often contain the exact fix. Read them.
Your task
This program doesn't compile (E0373). Make the closure take ownership of stock, so the thread can add it up. It should print total stock: 57.
Challenge 2
The main thread still needs names after spawning the worker (to print how many players there are), so moving it into the thread breaks the last line (E0382). Give the thread its own copy: clone the vector into a new variable before spawning, and move the copy. The program should print:
longest name: Hermione
players: 3
Challenge 3
A function can start a thread and hand the JoinHandle back to its caller, who decides when to wait. Write fn spawn_word_count(text: String) -> thread::JoinHandle<usize>: it starts a thread that counts the words in text (split on whitespace) and returns the handle without joining it.
Previous: Spawning a thread and joining it Next: Many threads, results in order