← Week 1: Tokio Fundamentals

Day 1: Async Rust — Future, Poll, and async/await

Phase 2 · Jun 10, 2026

← Week 1: Tokio Fundamentals

Agenda (2–3 hours)

  • Read (45 min): Rust async book chapters 1–3; std::future::Future trait documentation
  • Study (45 min): Desugar a simple async fn into a state machine by hand
  • Practice (45 min): Write three async functions: one that immediately returns, one that awaits a timer, one that awaits two concurrent operations with join!
  • Challenge (30 min): Implement a simple Future manually (without async/await) that resolves after a channel receives a value
← Week 1: Tokio Fundamentals

The Problem with Threads

For network I/O, threads are expensive:

  • Each thread ≈ 8MB stack + OS scheduling overhead
  • 10,000 concurrent connections → 80GB of stacks
  • Most threads are waiting (blocked on I/O), not running

Async I/O: instead of blocking a thread while waiting, register interest with the OS and get a callback. One OS thread can handle thousands of concurrent connections.

← Week 1: Tokio Fundamentals

The Future Trait

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    Ready(T),
    Pending,
}

A Future is a state machine. poll advances it one step:

  • Returns Poll::Ready(value) when complete
  • Returns Poll::Pending and arranges for cx.waker().wake() to be called when progress is possible

The runtime calls poll repeatedly; the Waker mechanism avoids spinning.

← Week 1: Tokio Fundamentals

async/await Desugaring

async fn fetch(url: &str) -> String {
    let response = http_get(url).await;
    response.body
}

Desugars to a struct implementing Future with states:

  1. Initial: hasn't called http_get yet
  2. Waiting for http_get: stores the in-progress future
  3. Complete: returns the body

The compiler generates the state machine automatically. await is a yield point.

← Week 1: Tokio Fundamentals

Pin and Unpin

Self-referential state machines (a future that contains a reference to its own field) cannot be moved in memory.

Pin<&mut Self> prevents the runtime from moving the future once it's been polled.

Most futures are Unpin (moving them is safe); the compiler inserts Pin automatically where needed.

In practice: use pin_mut! or Box::pin() when you need to store a future and await it later.

← Week 1: Tokio Fundamentals

Key Takeaways

  • Future is a poll-driven state machine; async/await generates the state machine automatically
  • Waker replaces callbacks: the runtime wakes up the right task when I/O is ready
  • Pin prevents moves of self-referential futures; usually transparent to application code
  • Async is cooperative: a task must await (yield) to allow others to run

Tomorrow: the Tokio runtime — how the executor, scheduler, and I/O reactor work together.