We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 2 of 24
move closures: why the compiler forces your hand
Split data into consecutive pieces, sum each piece on its own thread, and
return the per-piece sums in order.
pub fn own_and_sum(data: Vec<i64>, parts: usize) -> Vec<i64>
The exact contract
- If
parts == 0ordatais empty, return an empty vector. Check this first:data.chunks(0)panics. - Otherwise compute
size = data.len().div_ceil(parts), which isdata.len() / partsrounded up, and split withdata.chunks(size). Every chunk hassizeelements except possibly the last, which can be shorter. - This can produce fewer than
partschunks. Forlen = 5, parts = 4,sizeis 2, so the chunks have lengths[2, 2, 1]: three sums, not four. Whenpartsis larger thandata.len(),sizeis 1 and every element is its own chunk. - Return one sum per chunk, in chunk order (the order of
data), regardless of which thread finishes first. - Values can be negative. The tests keep every chunk sum within the
i64range, so you do not need to handle overflow.
Example: data = [1, 2, ..., 10], parts = 3 gives size = 4, chunks
[1..=4], [5..=8], [9, 10], and the result [10, 26, 19].
Why the starter does not compile
The starter is complete except for one keyword. Compiling it fails with error E0373:
error[E0373]: closure may outlive the current function, but it borrows
`piece`, which is owned by the current function
...
help: to force the closure to take ownership of `piece` (and any other
referenced variables), use the `move` keyword
Some terms first. A closure captures the variables it uses from its
surroundings. By default it captures them in the least demanding way the
body allows: piece.iter() only needs to read piece, so the closure
captures a reference to piece, not piece itself. Here piece is the
parameter of the outer .map(|piece| ...) closure, so it is destroyed when
that call returns, which happens right after the thread is started.
std::thread::spawn requires its closure type to be Send + 'static. A
'static bound on a type means the type contains no references that could
become invalid before the program ends; owned values such as Vec<i64> and
references to 'static data satisfy it. The bound exists because the thread
is not tied to the caller's stack frame: the returned JoinHandle can be
dropped, which detaches the thread, and the thread can keep running after
the function that started it has returned. A closure holding a reference to
piece does not satisfy 'static, so the compiler rejects it. That is the
meaning of "may outlive": the compiler does not claim the thread will run
that long, only that the type allows it.
Writing move || changes how the closure captures: it takes every captured
variable by value. The closure now owns the Vec<i64> named piece,
contains no borrowed references, and satisfies 'static. (It is also
Send, because Vec<i64> is Send.)
Moving means the caller gives the value up
After a value is moved into a closure, the original variable can no longer
be used. For example, this fails with E0382 because numbers was moved into
the thread:
let numbers = vec![1, 2, 3];
let handle = std::thread::spawn(move || numbers.iter().sum::<i64>());
println!("{}", numbers.len()); // error[E0382]: borrow of moved value: `numbers`
The starter avoids this: each piece is a separate vector created by
.to_vec(), used only by its own thread, and never read again by the
caller. The results come back as return values through join(), which
waits for the thread and returns Ok with the closure's result (or Err
if the thread panicked). If you hit E0382 while working with threads, a
useful question is which value you need back from the thread; returning it
through join() is often simpler than trying to keep using the moved
variable.
.to_vec() copies each chunk into a new vector. That copy is one simple way
to give each thread owned data; it is not the only one. Scoped threads
(std::thread::scope) can borrow data directly without copying, because
the scope waits for all of its threads before it returns.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.