A Tiny Async Runtime from Scratch
A Tiny Async Runtime from Scratch
Thesis
Async Rust is not magic: a Future is polled until it returns Ready; a runtime owns a queue of tasks, wakes them via Waker, and drives poll. Building a toy single-threaded executor demystifies Tokio.
Mental model
Task = Future + Waker plumbing
Executor: pop task → poll(cx) → Ready (done) | Pending (wait for wake)
Reactor / timer: signals Waker::wake → task re-queued
Worked example
// tiny_rt.rs — conceptual single-thread queue (std only)
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Wake, Waker};
struct MiniRuntime {
queue: VecDeque<Pin<Box<dyn Future<Output = ()> + 'static>>>,
}
impl MiniRuntime {
fn new() -> Self { Self { queue: VecDeque::new() } }
fn spawn<F: Future<Output = ()> + 'static>(&mut self, fut: F) {
self.queue.push_back(Box::pin(fut));
}
fn block_on<F: Future<Output = ()> + 'static>(&mut self, fut: F) {
self.spawn(fut);
while let Some(mut task) = self.queue.pop_front() {
// Real runtimes use proper Wakers; this busy-polls for teaching.
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
if task.as_mut().poll(&mut cx).is_pending() {
self.queue.push_back(task);
}
}
}
}
fn noop_waker() -> Waker {
struct N;
impl Wake for N { fn wake(self: Arc<Self>) {} }
Waker::from(Arc::new(N))
}
async fn demo() {
println!("hello from async");
}
fn main() {
MiniRuntime::new().block_on(demo());
}slept ~20ms
(If the lab captured a busy-loop note, that is expected: a teaching runtime without a reactor must either busy-poll or block on a condition variable.)
Trap
Equating async fn with threads. Without an executor, futures do nothing. Also: Pin exists because self-referential futures must not move; ignore it and you get UB, not a style warning.
Boring rule
Learn poll/Waker/queue first; then use Tokio/async-std for I/O. Never ship a busy-poll executor.
Try this
- Replace the noop waker with a channel-backed wake that requeues tasks.
- Add a timer wheel that wakes after
Duration. - Read Tokio’s
LocalSetdocs after the toy clicks.
Sources
- Async book
std::task/std::future- Tokio internals talks / docs on the reactor