We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 20 of 24
Safe parallel mutation with chunks_mut
Multiply each row of a grid by its own factor, using scoped threads to update disjoint bands of rows.
pub fn parallel_scale_rows(
grid: Vec<i64>,
width: usize,
factors: Vec<i64>,
workers: usize,
) -> Vec<i64>
The grid is stored row by row in one vector. For example, with width = 3,
[1, 2, 3, 4, 5, 6] contains two rows. With factors [2, 10], return
[2, 4, 6, 40, 50, 60].
Requirements
- Return an empty grid or a grid with
width == 0unchanged, without reading the factors. - Otherwise, the input contains complete rows:
grid.len()is a multiple ofwidth, andfactors.len()equals the number of rows. - Multiply cell
(r, c)byfactors[r]. All products fit ini64. - Treat zero workers as one, and cap the worker count at the number of rows. Split the rows into contiguous bands and spawn at most that many scoped threads. The result must be independent of the worker count.
- Mutate the original grid allocation. Do not copy the grid or use
unsafe,Arc, locks or atomics. Allocating a vector of borrowed row handles is fine.
The output tests check the returned values. They do not by themselves prove that an implementation uses threads or follows the allocation constraints; those are part of the implementation exercise.
Split the borrow before spawning
Each worker needs exclusive access to its own elements, not to the whole vector. Safe slice APIs can provide several mutable references at once when their regions do not overlap:
split_at_mut(mid)divides a slice into two parts at a valid boundary.chunks_mut(n)yields non-overlapping chunks, with a possibly shorter final chunk. The chunk size must be positive.iter_mut()yields a mutable reference to each element.
You can keep several chunks alive at the same time. For example:
fn main() {
let mut values = [1, 2, 3, 4];
let mut chunks = values.chunks_mut(2);
let first = chunks.next().unwrap();
let second = chunks.next().unwrap();
first[0] = 10;
second[0] = 30;
assert_eq!(values, [10, 2, 30, 4]);
}
Splitting encodes the non-overlap guarantee in the references you receive. You still need to choose the right boundaries and pair each row with the right factor. Memory safety does not establish those application rules.
Why the starter fails
The starter creates one &mut Vec<i64> named grid_ref, then tries to move
it into a new closure on every loop iteration. Rust reports E0382, a
use of a moved value: mutable references are not Copy, so the first closure
takes that reference and later closures cannot take it again.
The intended strided row indices do not give the closures separate borrows.
Give each closure an already disjoint slice instead. thread::scope allows
the threads to borrow local data because it joins them before returning;
it does not relax the rules for exclusive access.
Here is a complete example with two halves:
fn main() {
let mut values = [1, 2, 3, 4];
let (left, right) = values.split_at_mut(2);
std::thread::scope(|scope| {
scope.spawn(move || {
for value in left {
*value *= 2;
}
});
scope.spawn(move || {
for value in right {
*value *= 10;
}
});
});
assert_eq!(values, [2, 4, 30, 40]);
}
Build bands of rows
One approach is to collect grid.chunks_mut(width) into a
Vec<&mut [i64]>. This allocates row handles, not copies of the cells.
Apply chunks_mut(band_size) to that vector to borrow disjoint groups
of handles, then move each group into a scoped thread.
For a nonempty grid, first clamp the worker count as described above. Set
band_size = row_count.div_ceil(worker_count). This avoids the potentially
overflowing addition in (row_count + worker_count - 1) / worker_count.
Ten rows and six workers give bands of at most two rows, so this approach
starts five threads, which is within the requested limit.
Record each band's starting row before moving it into its closure. A row
at offset i within that band uses factors[start + i]. Workers can share
an immutable borrow of the factors while mutating separate grid rows.
Collecting handles is one option, not a requirement of chunks_mut.
You can also split the flat grid directly at row-aligned boundaries.
In either design, the scope creates fresh threads and waits for them;
it is not a reusable thread pool. For small grids, thread setup can cost
more than the multiplication, so this exercise teaches safe partitioning
rather than promising a speedup.
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.