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

  1. Never return Poll::Pending without scheduling a wakeup: If poll returns Poll::Pending without arranging for cx.waker().wake() (or wake_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!).
  2. Never poll a Future again after it has returned Poll::Ready(val): Once a future returns Poll::Ready(val), its state machine has consumed and moved out its final result; polling a completed async block a second time panics with "async fn resumed after completion".