We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 16 of 24
mpsc channels: message passing basics
One thread per value. Each doubles its value and sends it down a channel. The
main thread collects exactly values.len() results, sorts them, and returns.
pub fn fan_in(values: Vec<i64>) -> Vec<i64>
Use a counted recv() loop — for _ in 0..n { out.push(rx.recv()?) }.
Not for x in rx. That version is the next problem, and it hangs here; the
hang is worth meeting on its own terms rather than as an accident.
Why channels, when you already have Mutex
“Do not communicate by sharing memory; share memory by communicating.”
A channel moves ownership. You send(value) and the value is gone from
your thread — not copied, not shared, gone — and it arrives owned by the
receiver. There is no moment when two threads can both reach it, so there is
no lock discipline to maintain, no critical section to keep short, no lock
order to get wrong.
That matters more than it first appears. The problem with Arc<Mutex<T>> is
not correctness in the small; it is that the invariant “hold this lock before
touching that data” lives in every function that touches it, forever, in
everyone’s head. Channels replace that with the ownership rules you already
have. Channels scale better cognitively than shared state, and for a great
many workloads they are the right first answer.
The starter’s error
error[E0382]: use of moved value: `tx`
= note: move occurs because `tx` has type `Sender<i64>`, which does not
implement the `Copy` trait
mpsc stands for multi-producer, single-consumer. The multi-producer half
is delivered by Sender: Clone — clone one per thread, and they all feed the
same queue. Receiver is deliberately not Clone; there is exactly one
consumer, which is what makes “who receives this message” unambiguous.
let tx = tx.clone();
thread::spawn(move || { tx.send(v * 2).unwrap(); });
Sorting, and why the count is exact
Send order is the scheduler’s. Sorting collapses the possible interleavings onto a single expected value — the same determinism trick as earlier in this track.
The count is the other half. recv() blocks until a message arrives, so a
loop that runs exactly n times terminates exactly when all n messages have
been received, whatever order they came in. You are not polling and you are
not sleeping: the thread is parked by the OS until there is something to take.
The Sender and Receiver API, briefly
-
send(t)returnsResult<(), SendError<T>>, andSendErrorgives you the value back. If the receiver is gone, nobody can ever take this message, so std hands it to you rather than dropping it silently. That is a small design decision worth admiring. -
recv()returnsErr(RecvError)when everySenderhas been dropped. That is the only way it ends — see the next problem. -
try_recv()never blocks and distinguishesEmpty(nothing yet, senders still alive) fromDisconnected(nothing ever again). -
recv_timeout(d)adds aTimeoutvariant. -
SenderisSend + SyncandClone;ReceiverisSendbut notSyncand notClone.
::: question The main thread also still owns the original tx it cloned from. Does that break the counted loop?
No — and that is precisely why this problem uses a counted loop.
A counted loop stops after n messages regardless of who still owns a
Sender. It never asks “is the channel finished?”, so an extra live Sender
is harmless.
A for x in rx loop asks exactly that question, and it only ends when
every Sender has dropped — including the one the parent is still
holding. With the parent’s tx alive, that loop waits forever for a message
that will never come. Same program, same channel, one hangs and one does not.
That is the whole of the next problem, and the reason it is separate: the hang produces no error, no warning, and no clue. :::
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.