Rust Streams and StreamExt

The Stream trait

  • Lives in futures-core (not in std, not stabilized as of Rust 1.85)
  • Async equivalent of Iterator — yields items via poll_next()
  • You almost never call poll_next directly; you use combinator extensions

Two competing StreamExt traits

futures_util::StreamExt

  • Full combinator set: map, filter, take, next, collect, split, forward, zip, etc.
  • split() works on types that implement both Stream and Sink (e.g. a WebSocket). It consumes the value and returns two independent halves: a SplitStream (read side) and a SplitSink (write side). This lets you move each half into a separate concurrent task — you can’t do &mut socket in two places, but you can own sender in one task and receiver in another. See Axum WebSocket patterns for the concrete use case.
  • SinkExt provides .send() on the write half after splitting
  • Heavier dependency — pulls in all of futures-util

tokio_stream::StreamExt

  • Lighter subset from the tokio ecosystem
  • Has next, map, filter, take, collect, timeout — but no split()
  • Exists because tokio wanted a lighter dep than all of futures-util

Rule of thumb

If you already need futures-util (e.g., for split() or SinkExt on WebSocket split halves), just use futures_util::StreamExt everywhere. Mixing both causes ambiguity errors.

tokio_stream::wrappers

Adapters that wrap tokio types into Stream:

  • IntervalStream — wraps tokio::time::Interval
  • BroadcastStream — wraps broadcast::Receiver (used in Axum WebSocket patterns)
  • ReceiverStream — wraps mpsc::Receiver
  • UnboundedReceiverStream — wraps mpsc::UnboundedReceiver

futures_util::stream::unfold

State machine stream builder. Takes an initial state and an async closure:

use futures_util::stream::unfold;
 
let stream = unfold(0u32, |state| async move {
    if state < 5 {
        Some((state * 2, state + 1)) // (item_to_yield, next_state)
    } else {
        None // stream ends
    }
});
  • Returns Some((item, next_state)) to yield an item and continue
  • Returns None to end the stream
  • Useful for paginated API calls, file chunking, or any stateful iteration

See also