We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 4 of 24
Scoped threads: borrow, do not clone
Split data into contiguous chunks and return the maximum of each, in chunk
order — with no copying of the data.
pub fn chunk_maxes(data: Vec<i64>, workers: usize) -> Vec<i64>
Rules for this one:
-
everything happens inside a single
std::thread::scope; -
each worker thread borrows its
&[i64]— no.to_vec(), noArc, no cloning of any kind; -
chunk size is
data.len().div_ceil(workers), matchingslice::chunks, so you may end up with fewer thanworkerschunks; -
workers == 0and emptydataboth return[]. Guard them:chunks(0)panics and so doesdiv_ceil(0).
Why the previous problem had to copy
thread::spawn demands F: Send + 'static, and 'static means the closure
borrows nothing that could die first. The compiler has no idea when a spawned
thread ends — the handle can be dropped and the thread detached — so it must
assume the worst.
thread::scope (stable since 1.63) changes the premise. The scope joins
every thread it spawned before it returns, and that is enforced by the API,
not by convention. Because the compiler now knows every thread ends before the
scope does, the 'static bound is gone. Threads inside a scope may borrow
local data:
let data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| println!("{:?}", &data)); // borrowing a local. fine.
});
// every scoped thread has been joined by here
You still get handles back from s.spawn, and you still join them yourself
when you want a value. The automatic join at the end of the scope is a
guarantee about lifetimes, not a way to collect results in order.
This is the correct default for the shape of work in this track — read-only
input, split into disjoint pieces. Reach for Arc when you genuinely need
shared ownership, not as ceremony to appease 'static.
The clippy lesson, and it is the point of the starter
The starter computes the chunk size the way every C programmer’s fingers do:
let size = (data.len() + workers - 1) / workers;
It compiles. It is arithmetically correct for the inputs here. And it fails
the gate, because clippy::manual_div_ceil has been warn-by-default since
1.83:
warning: manually reimplementing `div_ceil`
|
| let size = (data.len() + workers - 1) / workers;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| help: consider using `.div_ceil()`: `data.len().div_ceil(workers)`
Take this one seriously rather than treating it as style. len + workers - 1
can overflow for large len, and overflow checks are off in this build,
so it would wrap silently and hand you a chunk size near zero. div_ceil
cannot overflow. The lint is protecting you from a real bug that would only
show up on large inputs, and clippy caught it without running your code.
The other error worth meeting
If you try to accumulate into a shared local from two scoped threads:
let mut count = 0;
thread::scope(|s| {
s.spawn(|| count += 1);
s.spawn(|| count += 1); // error[E0499]: cannot borrow `count` as
}); // mutable more than once at a time
thread::scope relaxed 'static. It did not relax aliasing-xor-mutation
— two &mut to the same local is still two &mut to the same local, whether
or not threads are involved. That is the borrow checker preventing a data race
with a rule it already had, which is a fair summary of why “fearless
concurrency” is not a separate feature bolted on.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.