Channels
Message passing lets threads communicate by sending owned data through channels instead of sharing mutable state. Rust's standard library provides mpsc; the ecosystem adds bounded queues, select, and more.
Busca en todas las páginas de la documentación
Message passing lets threads communicate by sending owned data through channels instead of sharing mutable state. Rust's standard library provides mpsc; the ecosystem adds bounded queues, select, and more.
Quick-reference recipe card - copy-paste ready.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(42).unwrap();
});
println!("{}", rx.recv().unwrap());
}When to reach for this: When workers produce results, pipeline stages hand off work, or you want to avoid locks and shared mutation.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let producer = thread::spawn(move || {
for i in 0..5 {
tx.send(i).unwrap();
thread::sleep(Duration::from_millis(10));
}
});
for msg in rx {
println!("received {msg}");
}
producer.join().unwrap();
}What this demonstrates:
mpsc::channel creates an unbounded queue (memory can grow).send moves ownership; the sender can be cloned for multiple producers.rx blocks until senders are dropped and the queue is empty.Copy). No locks on the message body once sent.mpsc has one receiver. Multiple receivers need crossbeam-channel or fan-out patterns.recv returns Err when all senders are dropped.| Feature | std::sync::mpsc | crossbeam-channel |
|---|---|---|
| Bounded queue | no | yes |
| Multiple consumers | no | yes |
select! / try_recv batch | limited | yes |
| Ecosystem default | std only | common in production |
// Multiple producers: clone the sender
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || { tx.send(1).unwrap(); });
thread::spawn(move || { tx2.send(2).unwrap(); });
drop(tx); // drop original so rx iterator can finishSender before moving into each thread.recv can return Disconnected.crossbeam_channel::bounded(n) when producers can outpace consumers.recv blocks forever if a clone still exists. Fix: Track sender handles and drop extras explicitly.Send. Fix: Use Arc or restructure; do not send Rc across threads.send after disconnect - Receiver gone means send fails. Fix: Handle Err or use let _ = tx.send(...) with logging.| Alternative | Use When | Don't Use When |
|---|---|---|
Arc<Mutex<T>> | Shared state with infrequent updates | Many producers writing independent messages |
crossbeam-channel bounded | Production pipelines with backpressure | You only need std and unbounded is fine |
Tokio mpsc | Async services | Pure sync thread code |
| Shared memory ring buffer | Ultra-low latency, fixed capacity | You need dynamic sizing or many consumers |
Start with std::sync::mpsc for learning and simple tools. Use crossbeam-channel when you need bounded queues, multiple consumers, or select.
Not with std mpsc - only one Receiver. Split work at the sender side or use crossbeam multi-consumer patterns.
send returns Err(SendError(msg)) and you recover ownership of the message.
For independent messages, channels avoid lock contention. For hot shared counters, atomics or a single mutex may be simpler.
Send a sentinel message, use a bool flag in an Arc<AtomicBool>, or drop senders and let recv return Disconnected.
Yes. Many pipelines send Result<T, E> so workers report per-item failures.
sync_channel(n) blocks after n outstanding messages - built-in backpressure.
Similar philosophy (share by communicating). Rust channels move ownership and are typed; bounded backpressure is explicit.
Do not block an async executor on std::sync::mpsc::recv. Use tokio::sync::mpsc in async code.
Use short timeouts (recv_timeout) or bounded channels in tests to avoid hangs when a bug leaves a sender alive.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026