Introduction
Streams shine in real-world scenarios: paginating APIs, processing files line-by-line, handling WebSocket connections, and event sourcing. These patterns appear constantly in production Rust services.
Key Concepts
Pagination — yield pages of results as a stream:
rustuse async_stream::stream; fn paginated(api: &Api) -> impl Stream<Item = Item> + '_ { stream! { let mut page = 1; loop { let items = api.fetch_page(page).await; if items.is_empty() { break; } for item in items { yield item; } page += 1; } } }
Real World Context
Every API with pagination, every file processor, every message queue consumer, every real-time feed is naturally modeled as a stream.
Deep Dive
File processing:
rustuse tokio::io::{AsyncBufReadExt, BufReader}; use tokio_stream::wrappers::LinesStream; async fn process_file(path: &str) -> Result<(), Error> { let file = tokio::fs::File::open(path).await?; let mut lines = LinesStream::new(BufReader::new(file).lines()); while let Some(line) = lines.next().await { process_line(&line?).await?; } Ok(()) }
WebSocket messages:
rustasync fn handle_ws(ws: WebSocket) { let (mut tx, mut rx) = ws.split(); while let Some(Ok(msg)) = rx.next().await { if let Message::Text(text) = msg { let response = process(&text).await; tx.send(Message::Text(response)).await.ok(); } } }
Event sourcing with broadcast:
rustlet stream = BroadcastStream::new(event_rx); stream .filter_map(|r| async { r.ok() }) .for_each(|event| async { handle_event(event).await }) .await;
Common Pitfalls
- Not handling stream errors — use
filter_mapor explicit error handling - Unbounded stream processing — add backpressure with
buffer_unordered - Creating streams that never end without a shutdown mechanism
Best Practices
- Use
async_stream::stream!for custom pagination and generation logic - Wrap channel receivers with
ReceiverStreamorBroadcastStream - Always plan for stream termination (channel close, EOF, shutdown signal)
Summary
Streams model pagination, file I/O, WebSockets, and event sourcing naturally. Use async_stream::stream! for generators. Wrap channels with stream wrappers. Always handle errors and plan termination.
Code Examples
rust
use async_stream::stream;
use tokio_stream::StreamExt;
// Paginated API consumer as a stream
fn fetch_all_users(client: &Client) -> impl Stream<Item = User> + '_ {
stream! {
let mut cursor = None;
loop {
let page = client.get_users(cursor).await.unwrap();
cursor = page.next_cursor;
for user in page.users {
yield user;
}
if cursor.is_none() { break; }
}
}
}
async fn example(client: &Client) {
let mut users = std::pin::pin!(fetch_all_users(client));
while let Some(user) = users.next().await {
println!("{}", user.name);
}
}