Asynchronous Rust
The Future Trait
std::future::Future is the single core trait in the Rust standard library that powers all asynchronous programming. Every async fn, async { ... } block, and custom asynchronous operation implements Future.
Exact Definition of std::future::Future and std::task::Poll
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T),
Pending,
}
| Component | Role in Future::poll |
|---|---|
type Output |
The value type produced once the asynchronous computation finishes |
self: Pin<&mut Self> |
Pinned mutable receiver guaranteeing that the future's memory address will not move between polls |
cx: &mut Context<'_> |
Carries the Waker (cx.waker()) that the future must register to notify the executor when it is ready to be polled again |
Poll::Ready(val) |
The computation is complete and yields val: Self::Output |
Poll::Pending |
The computation cannot make further progress right now; it has registered cx.waker() to be woken when progress becomes possible |
Implementing Future Manually on a Struct
Implementing Future directly demonstrates the exact contract between a leaf future and the executor:
use std::future::Future;
use std::pin::{Pin, pin};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::task::{Context, Poll, Waker};
struct ThresholdFuture {
counter: Arc<AtomicU32>,
target: u32,
}
impl Future for ThresholdFuture {
type Output = u32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let current = self.counter.load(Ordering::Acquire);
if current >= self.target {
Poll::Ready(current)
} else {
// In a real I/O or timer future, store `cx.waker().clone()` so the
// background reactor/thread can wake the task when `counter` changes:
let _waker_clone: Waker = cx.waker().clone();
Poll::Pending
}
}
}
fn main() {
let shared_counter = Arc::new(AtomicU32::new(5));
let mut fut = pin!(ThresholdFuture {
counter: Arc::clone(&shared_counter),
target: 10,
});
let mut cx = Context::from_waker(Waker::noop());
println!("poll 1 (counter = 5): {:?}", fut.as_mut().poll(&mut cx));
shared_counter.store(12, Ordering::Release);
println!("poll 2 (counter = 12): {:?}", fut.as_mut().poll(&mut cx));
}
poll 1 (counter = 5): Pending
poll 2 (counter = 12): Ready(12)
Two Critical Rules of the Future::poll Contract
- Never return
Poll::Pendingwithout scheduling a wakeup: IfpollreturnsPoll::Pendingwithout arranging forcx.waker().wake()(orwake_by_ref()) to be called when the awaited event occurs, the executor will put the task to sleep and never poll it again (hanging the task forever!). - Never poll a
Futureagain after it has returnedPoll::Ready(val): Once a future returnsPoll::Ready(val), its state machine has consumed and moved out its final result; polling a completedasyncblock a second time panics with"async fn resumed after completion".