In this lesson, you will learn how to implement custom Waker types to bridge the gap between low-level blocking events and the asynchronous task execution model.
The Role of the Waker
When a Future returns Poll::Pending, it is signaling that it cannot proceed until some external event occurs. But how does the executor know when to try polling that Future again? This is the job of the Waker.
A Waker is a handle that, when triggered via its wake() method, tells the executor: "The resource this task was waiting on is ready; please poll it again." The Waker is essentially a pointer to a specific task, wrapped in a thread-safe, heap-allocated container that implements the RawWaker trait.
The Waker Contract
To create a custom Waker, you must adhere to the RawWaker contract. You provide a vtable of function pointers that define how to clone, wake, wake_by_ref, and drop your waker state.
Consider a simple scenario where a background thread needs to wake a task:
use std::task::{RawWaker, RawWakerVTable, Waker};
// 1. The custom state that tracks the task
struct MyTask { /* ... */ }
// 2. The vtable defines how to manipulate the state
static VTABLE: RawWakerVTable = RawWakerVTable::new(
clone_waker, wake, wake_by_ref, drop_waker,
);
fn wake(ptr: *const ()) {
// Safety: Cast the pointer back to the task and signal the executor
let task = unsafe { &*(ptr as *const MyTask) };
task.signal_executor_to_poll();
}
// 3. Create the Waker from the raw parts
unsafe fn create_waker(task: *const MyTask) -> Waker {
let raw = RawWaker::new(task as *const (), &VTABLE);
Waker::from_raw(raw)
}
The contract is strict: wake() consumes the Waker, while wake_by_ref() does not. If your implementation violates the memory safety expectations of these functions (like double-freeing the task pointer or leaking memory), the entire async runtime will behave unpredictably. You are responsible for ensuring that the task pointer remains valid until drop is called on the Waker. By manually implementing these, you gain fine-grained control over how your tasks are queued, allowing for optimizations like task-priority queuing or thread-local scheduling.
Key Takeaway: The Waker acts as a thread-safe callback that notifies the executor to resume a suspended task, requiring strict adherence to the RawWaker vtable contract to manage task lifetimes correctly.