Rust — Channels
"Share memory by communicating"
Rust concurrency philosophy: instead of threads fighting over shared memory, pass ownership of messages between them. Channels are the pipes.
use std::sync::mpsc; // multi-producer, single-consumer
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel(); // transmitter + receiver
thread::spawn(move || {
tx.send("hello from worker").unwrap();
});
println!("{}", rx.recv().unwrap()); // blocks until a message arrives
}
send MOVES the value into the channel — the sending thread loses it instantly. Ownership literally travels through the pipe:
let val = String::from("data");
tx.send(val).unwrap();
// println!("{}", val); ❌ moved into the channel!
That's why data races via channel misuse can't happen — only one side ever owns a message.
recv vs try_recv
| Method | Behavior |
|---|---|
rx.recv() | blocks until message or channel closed → Result |
rx.try_recv() | returns immediately: Ok(msg) / Err(Empty) / Err(Disconnected) |
Multiple messages — treat rx as an iterator
for received in rx {
println!("got {}", received);
}
The loop ends automatically when ALL transmitters drop (channel closes):
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for n in 1..=5 {
tx.send(n).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
}); // tx dropped here when thread ends
for received in rx { // runs 5 times, then exits cleanly
println!("received {}", received);
}
Keep tx alive in main and the loop waits forever instead — lifetime of senders controls closure of receivers.
Multiple producers
mpsc = multi-producer: clone the transmitter for each worker:
let (tx, rx) = mpsc::channel();
for id in 0..3 {
let tx = tx.clone(); // each thread gets its own handle
thread::spawn(move || {
tx.send(format!("worker {} done", id)).unwrap();
});
}
drop(tx); // release original so rx can finish
for msg in rx {
println!("{}", msg);
}
The explicit drop(tx) matters: with the original still alive in main, for msg in rx would never end even after workers finish.
When channels beat shared state
✓ Worker pools distributing jobs
✓ Streaming results back to a collector
✓ Event systems between components
✗ Shared counters/cache → Arc<Mutex<T>> fits better
Rust book's motto: share memory by communicating. Design systems as message-passers first; reach for Mutex only when sharing is genuinely simpler.
Gotchas: forgetting to close/drop tx hangs receivers · send() errors when receiver dropped ("receiver disconnected") · mpsc receiver is NOT clonable — one consumer; crossbeam-channel lifts that limit.
Mini Practice
- Send three strings across a channel; print on arrival.
- Producer with sleep delays; watch iterator block per message.
- Three producer threads + one consumer collecting all results.
- Demonstrate send-fails-after-drop error.
- Rewrite the Arc<Mutex> counter exercise using a channel aggregator.
Next: testing →
Related Topics
Frequently Asked Questions about Channels
What is Channels in Rust?
Channels is a fundamental concept in Rust. This lesson explains it step by step with clear examples, making it easy for beginners to understand.
How do I learn Channels?
Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Channels.
Why is Channels important in Rust?
Channels is essential for Rust development. Understanding this concept will help you write better code and solve real-world problems more effectively.