Skip to content

Q059. How does Future::poll work?

Short Answer

An executor calls poll with a pinned mutable reference to the future and the current task's Context. The future performs available work and returns Ready(output) or Pending. Before returning Pending, it must arrange for the task's current Waker to be notified when progress may be possible. poll must not block, and executors should poll again because of a wake-up rather than busy-looping.

Deep Dive

One call to poll can run through many immediately ready operations. .await is not an unconditional yield: if the child is ready, the parent continues in the same poll.

When a child is pending, the parent also returns pending after preserving state. The waker contract prevents lost progress. A future should replace an older stored waker when the current one differs because a future can move between tasks or executors before being pinned and polled.

After returning Ready, the future is complete. The Future trait does not define safe behavior for another poll; generated async futures commonly panic if polled after completion.

Internal Model

Pin<&mut Self> lets the future mutate its state while preserving the address of any structurally pinned fields. Context currently exposes the task's Waker.

Poll is an enum. Returning it is an ordinary function return; the scheduler behavior comes from executor code around the call.

Example

rust
use std::task::Poll;

#[tokio::main]
async fn main() {
    let value = std::future::poll_fn(|_context| Poll::Ready(42_u64)).await;
    assert_eq!(value, 42);
}

Common Mistakes

  • Blocking the thread until an operation completes inside poll.
  • Returning Pending without arranging a future wake-up.
  • Assuming every .await causes the executor to switch tasks.

Follow-ups

  • Why does a future need the latest waker?
  • What prevents an executor from continuously polling every pending future?
  • Why is polling after Ready generally invalid?

References

  • Rust Standard Library
  • Async Book