Course contentsShow
← Rust
Lesson 1244 of 1,91011. Asynchronous ProgrammingFree lesson

Error Handling in Asynchronous Streams

Implement robust error propagation patterns that respect the Future polling lifecycle.

In this lesson, you will learn how to design robust error propagation strategies that ensure your asynchronous streams handle failures without stalling the executor's polling lifecycle.

When working with Stream, a common pitfall is treating an error as a terminal state that silently drops the remaining events. Because a Stream is polled by an executor, if an error causes your task to return Poll::Ready(None) prematurely, the stream terminates. To build production-grade systems, you must distinguish between transient errors (which should be retried) and fatal errors (which should be propagated).

The Pattern: Wrapping Results

The most resilient approach is to define the Item of your stream as a Result<T, E>. This allows the consumer of the stream to differentiate between data arriving successfully and an internal failure within the stream's state machine.

Consider a sensor stream where individual reads might fail:

use futures::stream::{Stream, StreamExt};
use std::pin::Pin;
use std::task::{Context, Poll};

struct SensorStream { /* ... */ }

impl Stream for SensorStream {
    type Item = Result<f64, SensorError>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        match self.read_hardware() {
            Ok(value) => Poll::Ready(Some(Ok(value))),
            Err(e) if e.is_transient() => {
                cx.waker().wake_by_ref(); // Re-schedule to retry immediately
                Poll::Pending
            }
            Err(e) => Poll::Ready(Some(Err(e))), // Propagate fatal error
        }
    }
}

Respecting the Polling Lifecycle

When you encounter an error, you must decide if the stream can recover. If you return Poll::Pending because of a transient error, you must ensure a Waker is registered or triggered. If you simply return Poll::Pending without notifying the executor, you create a "zombie" stream that will never be polled again, effectively leaking the task.

By mapping errors into the Item type, you keep the stream alive while allowing downstream operators—like filter_map or try_for_each—to handle the logic of whether to halt execution or log and continue.

Key Takeaway: Always lift errors into the stream’s Item type and ensure that every transition to Poll::Pending is accompanied by a registered Waker to prevent deadlocked event loops.