Streams
Stream is the async cousin of Iterator: it produces a sequence of items over time, driven by .await instead of .next(). Use streams for paginated APIs, WebSockets, file chunks, and event feeds.
Recipe
Quick-reference recipe card - copy-paste ready.
use tokio_stream::{self as stream, StreamExt};
#[tokio::main]
async fn main() {
let mut s = stream::iter(vec![10, 20, 30]);
while let Some(n) = s.next().await {
println!("{n}");
}
}When to reach for this: Consuming paginated HTTP, database cursors, Kafka/NATS messages, or SSE/WebSocket events.
Working Example
use tokio::sync::mpsc;
use tokio_stream::{StreamExt, wrappers::ReceiverStream};
async fn produce(tx: mpsc::Sender<i32>) {
for i in 1..=5 {
tx.send(i).await.unwrap();
}
}
#[tokio::main]
async fn main() {
let (tx, rx) = mpsc::channel(8);
tokio::spawn(produce(tx));
let mut stream = ReceiverStream::new(rx);
let sum: i32 = stream.map(|n| n * 2).sum().await;
println!("{sum}");
}What this demonstrates:
- Channel receiver adapts to
StreamviaReceiverStream. StreamExtprovidesmap,filter,take,buffer_unordered.sum().awaitconsumes the stream.
Deep Dive
How It Works
Stream::poll_next: LikeFuture::pollbut yieldsOption<Item>untilNone.- Backpressure: Slow consumers naturally apply pressure on bounded channels.
- Fusion with futures: Many apps map streams to responses (Axum SSE, gRPC streaming).
StreamExt: Ergonomic methods fromfutures/tokio-streamcrates.
Common Adapters
| Adapter | Purpose |
|---|---|
map / filter | Transform items |
take(n) | Limit count |
timeout | Per-item deadline |
buffer_unordered(n) | Concurrent map with limit |
Rust Notes
use futures::Stream;
use std::pin::Pin;
use std::task::{Context, Poll};
struct Counter {
n: u32,
max: u32,
}
impl Stream for Counter {
type Item = u32;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<u32>> {
if self.n >= self.max {
Poll::Ready(None)
} else {
let v = self.n;
self.n += 1;
Poll::Ready(Some(v))
}
}
}- Manual
Streamimpls mirrorFuturepinning rules. - Prefer adapters over manual impls in application code.
try_stream!macro builds fallible streams ergonomically.
Gotchas
- Unbounded production - Fast producer overwhelms memory without bounds. Fix: Bounded
mpscorstream::iterwith rate limits. - Forgetting to drive stream - Creating stream without consuming leaks tasks. Fix: Always drain, cancel, or drop explicitly.
- Blocking in stream adapters -
mapclosures must not block. Fix:thenwith async offload. - Mixing Stream crates - Import
StreamExtfrom the crate matching your stream type. Fix:use tokio_stream::StreamExtvsfutures::StreamExt(enable features). - No
for awaitin stable Rust - Usewhile let SomeorStreamExt::next. Fix: Loop with.awaitonnext().
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Vec + loop | Small finite batches | Live unbounded events |
| Channels only | Task coordination | You need iterator-style adapters |
| Polling loop + sleep | Simple cron tick | High-throughput event processing |
| Callback API | C FFI bridge | Idiomatic Rust async services |
FAQs
Stream vs Iterator?
Iterator is synchronous pull. Stream is async pull - each item may wait for I/O.
Which StreamExt?
tokio_stream::StreamExt for Tokio streams; futures::StreamExt is broader - import one consistently.
Axum streaming bodies?
Axum can return Body::from_stream wrapping a Stream<Item = Result<Bytes, Error>>.
sqlx streams?
sqlx::query(...).fetch(&pool) returns a stream of rows - process with try_next().await.
Merge multiple streams?
select! on streams, StreamExt::merge, or futures::stream::select for fair merge.
Backpressure?
Bounded channels block producers; buffer_unordered limits in-flight async maps.
Error handling?
TryStream with try_next().await or map_err + ? inside try_stream!.
Testing streams?
stream::iter(vec![...]) for deterministic inputs; timeout in tests to catch hangs.
pin_project?
Advanced streams use pin_project for pin-projecting struct fields in poll_next.
WebSocket?
tokio-tungstenite messages form a stream of frames - map to app events.
Related
- Async Basics - async iteration intro
- Combining Futures - select with streams
- Channels in Tokio - ReceiverStream
- Async I/O - byte streams
- Cancellation & Timeouts - stream timeouts
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+.