Streams in Rust
In this lesson, you will learn about Streams in Rust. Streams represent a sequence of asynchronous values that become available over time.
They are similar to iterators, but work in an asynchronous context.
What Is a Stream?
A stream is an asynchronous sequence of values. Instead of producing all values at once, a stream yields values one by one as they are ready.
Streams are commonly used for:
- Receiving data from network connections
- Processing asynchronous events
- Handling continuous data flows
- Working with async I/O
Streams vs Iterators
Iterators work in synchronous code, while streams work in asynchronous code.
- Iterator: values are immediately available
- Stream: values arrive asynchronously
Streams require an async runtime like Tokio to operate.
The Stream Trait
Streams are defined using the Stream trait from the futures crate.
It is similar to the Iterator trait, but uses poll_next().
use futures::stream::Stream;
Each call attempts to produce the next value asynchronously.
Using Streams with Tokio
Tokio provides utilities for working with streams efficiently.
To consume a stream, you typically use while let with .next().await.
use futures::stream::StreamExt;
while let Some(value) = stream.next().await {
println!("{}", value);
}
Creating a Stream
You can create a stream from a collection using stream::iter.
use futures::stream;
let numbers = stream::iter(vec![1, 2, 3, 4]);
Each value is yielded asynchronously.
Async Streams with Delays
Streams often work together with timers.
use tokio::time::{sleep, Duration};
use futures::stream::StreamExt;
let mut stream = futures::stream::iter(1..=3);
while let Some(n) = stream.next().await {
sleep(Duration::from_secs(1)).await;
println!("{}", n);
}
This produces one value per second.
Stream Combinators
Streams support powerful combinators similar to iterators.
map()filter()take()for_each()
use futures::stream::StreamExt;
let stream = futures::stream::iter(1..=5)
.map(|x| x * 2);
stream.for_each(|x| async move {
println!("{}", x);
}).await;
Error Handling in Streams
Streams can produce Result values.
This allows graceful handling of errors in async pipelines.
while let Some(result) = stream.next().await {
match result {
Ok(value) => println!("{}", value),
Err(err) => eprintln!("{}", err),
}
}
When to Use Streams
Streams are ideal for:
- Event-driven systems
- Async networking
- Data pipelines
- Real-time processing
📝 Practice Exercises
Exercise 1
What is the main difference between a stream and an iterator?
Exercise 2
How do you consume values from a stream?
Exercise 3
Name two common stream combinators.
Exercise 4
Why are streams useful in async programming?
✅ Practice Answers
Answer 1
Streams produce values asynchronously, while iterators are synchronous.
Answer 2
Using while let Some(value) = stream.next().await.
Answer 3
map() and filter().
Answer 4
They allow handling sequences of async values efficiently.
What’s Next?
In the next lesson, you will learn about the Select Macro, which allows working with multiple async operations simultaneously.