Custom Future
Implement a Custom Future (Pin, Waker, Poll)
When you need low-level control: a custom hardware driver, a specialized timer,
or a zero-allocation protocol parser—you can implement Future by hand
instead of relying on async/await.
Every custom future must handle three concepts:
| Concept | Why it matters |
|---|---|
Pin<&mut Self> | Guarantees the future won't move in memory after the first poll. This is critical for self-referential futures (e.g., those holding borrows across .await points). If your struct contains only ordinary fields (no self-references), the compiler auto-implements Unpin and pinning is effectively a no-op. |
Poll::Pending / Poll::Ready | Pending tells the executor "not done yet," while Ready(value) completes the future. |
cx.waker() | A handle the executor gives you. You must call wake() at some point after returning Pending, or the executor will never poll the future again and it will hang. |
The example below builds a simple Delay future that resolves after a
deadline. It has no self-referential fields, so it is Unpin automatically—
pinning costs nothing. A production timer would register with a reactor;
here we call wake_by_ref() immediately so the executor re-polls us.
use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; /// A hand-rolled future that resolves after a deadline. /// /// This is intentionally simple—no timer wheel, no reactor—just a /// busy-polling future that shows the three things every custom /// `Future` must handle: /// /// 1. **`Pin<&mut Self>`** — guarantees our struct won't move in /// memory. Because `Delay` contains only `Instant` and /// `Duration` (both `Unpin`), the compiler auto-implements /// `Unpin` for us and pinning is a no-op here. If the struct /// held a self-referential borrow (like a hand-written async /// state machine), pinning would *prevent* the struct from /// being moved after the first poll, which would invalidate /// the borrow. /// /// 2. **`Poll::Pending` vs `Poll::Ready`** — returning `Pending` /// tells the executor "I'm not done yet; wake me later." /// /// 3. **`cx.waker()`** — the mechanism to *schedule* a re-poll. /// Without calling `wake()`, the executor would never poll us /// again and the future would hang. struct Delay { /// When we should resolve. deadline: Instant, } impl Delay { fn new(dur: Duration) -> Self { Self { deadline: Instant::now() + dur, } } } impl Future for Delay { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { if Instant::now() >= self.deadline { // Deadline reached — resolve the future. Poll::Ready(()) } else { // Not ready yet. // // We MUST arrange for `wake()` to be called later or the // executor will never poll us again. A production timer // would register with a reactor; here we just ask to be // re-polled immediately. The executor may yield to other // tasks before coming back to us. cx.waker().wake_by_ref(); Poll::Pending } } } #[tokio::main] async fn main() { let start = Instant::now(); println!("waiting 10 ms …"); // Use the custom future just like any other async expression. Delay::new(Duration::from_millis(10)).await; println!("done in {:?}", start.elapsed()); assert!(start.elapsed() >= Duration::from_millis(10)); }