Message Passing

Sending and Receiving Messages

Sending and receiving messages over an mpsc channel offers both blocking and non-blocking / timed methods on the transmitter and receiver halves to match different concurrency loops.

Complete Comparison of Send Methods

Method Receiver Type Blocks Caller? Return Type When It Fails
Sender::send(val) Sender<T> Never Result<(), SendError<T>> Only if Receiver<T> has been dropped
SyncSender::send(val) SyncSender<T> Yes (when buffer is full) Result<(), SendError<T>> Only if Receiver<T> has been dropped
SyncSender::try_send(val) SyncSender<T> Never Result<(), TrySendError<T>> TrySendError::Full(val) if full, or TrySendError::Disconnected(val) if Receiver dropped

Complete Comparison of Receive Methods (Receiver<T>)

Method Blocks Caller? Return Type Behavior When Queue Is Empty
.recv() Yes Result<T, RecvError> Parks calling thread until a message arrives or all Senders drop
.try_recv() Never Result<T, TryRecvError> Returns Err(TryRecvError::Empty) immediately if no message is ready (or Err(TryRecvError::Disconnected) if all Senders dropped)
.recv_timeout(dur) Up to dur Result<T, RecvTimeoutError> Blocks for at most dur; returns Err(RecvTimeoutError::Timeout) if time expires
.iter() / for msg in &rx Yes Iter<'_, T> Yields messages until all Senders drop (keeps rx usable afterward)
.try_iter() Never TryIter<'_, T> Drains all currently queued messages without blocking and stops immediately when queue is empty

Draining Currently Queued Messages Without Blocking (try_recv and try_iter)

In game loops, UI render loops, or batch-processing workers, calling rx.try_iter() drains all messages that have accumulated since the last tick without blocking the thread if the queue is empty:

use std::sync::mpsc::{self, TryRecvError};
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel::<u32>();

    // Queue 3 messages before polling:
    tx.send(10).unwrap();
    tx.send(20).unwrap();
    tx.send(30).unwrap();

    // 1. Drain all currently available messages without blocking via `.try_iter()`:
    let batch: Vec<u32> = rx.try_iter().collect();
    println!("non-blocking drained batch: {:?}", batch);

    // 2. Subsequent `.try_recv()` sees an empty queue while `tx` is still alive:
    match rx.try_recv() {
        Ok(val) => println!("unexpected value: {}", val),
        Err(TryRecvError::Empty) => println!("try_recv: queue is currently Empty (tx still alive)"),
        Err(TryRecvError::Disconnected) => println!("try_recv: channel Disconnected"),
    }

    // 3. Timed receive waiting at most 1 millisecond:
    let timeout_res = rx.recv_timeout(Duration::from_millis(1));
    println!("recv_timeout on empty queue: {:?}", timeout_res);
}
non-blocking drained batch: [10, 20, 30]
try_recv: queue is currently Empty (tx still alive)
recv_timeout on empty queue: Err(Timeout)