We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 8 of 24
unsafe impl Send, and the disjoint-capture trap
Scatter a list of (index, value) writes into a zeroed buffer, in parallel,
through a raw pointer.
pub fn scatter_unsafe(len: usize, writes: Vec<(usize, i64)>, workers: usize) -> Vec<i64>
Allocate vec![0i64; len], split 0..len into workers disjoint
half-open ranges, and give every worker the same raw *mut i64. Each worker
scans the whole writes list in order and applies only the writes whose
index lands in its own range. Return the buffer.
Indices are always < len. Duplicate indices are allowed and the last write
in input order wins — which is automatic if you keep the scan in order,
because every index belongs to exactly one worker.
len == 0 returns []; treat workers == 0 as 1.
This problem can produce undefined behaviour, and that is the point
Everything else in this track is safe code where the compiler proves you
right. Here you are making the promise instead. If your ranges overlap, two
threads write the same i64 concurrently, and that is a data race — undefined
behaviour, not “a wrong number”. The grader cannot detect it. It may pass.
It may pass on your machine and fail on someone’s ARM laptop. This is the real
cost of unsafe, and meeting it deliberately once is worth more than reading
about it ten times.
Two problems from now you will write the same computation with chunks_mut,
where the borrow checker proves disjointness for you and no unsafe appears
at all. Compare them when you get there.
Why the newtype exists
*mut i64 is not Send. Auto traits are structural: a closure capturing
a raw pointer is not Send, so spawn refuses it. That is the compiler
saying “I have no idea whether this is safe”, which is correct — a raw pointer
carries no information about who else can reach it.
The escape hatch is to make the promise yourself:
struct SendPtr(*mut i64);
unsafe impl Send for SendPtr {}
unsafe impl is a proof obligation, not an escape hatch. You are telling
the compiler you have an argument it cannot see. Write that argument down —
the reference solution carries a // SAFETY: comment naming both facts the
proof rests on (disjoint ranges, all threads joined before the buffer is read).
If you cannot write the comment, you do not have the proof.
The trap — and it will get you
The starter has the unsafe impl. It still fails:
error[E0277]: `*mut i64` cannot be sent between threads safely
= help: within `{closure@...}`, the trait `Send` is not implemented for `*mut i64`
SendPtr is Send. So why is the closure complaining about *mut i64?
Because since edition 2021, closures capture individual fields, not whole
variables (RFC 2229, “disjoint capture”). The closure body mentions p.0
and nothing else about p, so what it captures is p.0 — a bare *mut i64,
which is not Send. Your unsafe impl applies to SendPtr, and no SendPtr
was ever captured.
This change was made for good reasons — it lets two closures borrow different fields of one struct — and here it silently defeats a pattern that used to work. The fix is to make the closure name the whole value. The blunt version is one line at the top of the closure:
s.spawn(move || {
let p = p; // forces the whole SendPtr to be captured
...
});
The tidier version is to make the field private and hand out the pointer
through a method — p.get() takes &self, so the whole struct is captured.
Either works. Knowing why either works is the item.
Two lints that will not save you here
Worth knowing what the toolchain is and is not doing for you:
-
clippy::non_send_fields_in_send_tywould flag exactly this kind ofunsafe impl. It is nursery, so it is off by default and will not fire under this gate. -
clippy::undocumented_unsafe_blockswould force the// SAFETY:comments. It is restriction, also off by default.
Neither is a criticism of clippy. It is a calibration: on the unsafe side of
the line, the tools have far less to say, and the discipline has to come from
you.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.