the executor / runtime that drives async tasks
A stage play has actors who know their lines, but nothing happens until a director calls each one on at the right moment, and a stage manager watches for cues from offstage. An async program is the same: your async functions are coroutines that know how to suspend and resume, but they are inert by themselves. The executor is the director that actually runs them — it picks ready tasks and drives each one forward until it next suspends. The runtime is the whole production: executor plus the reactor that watches for the events tasks are waiting on.
Here is the loop, concretely. The executor holds a queue of ready tasks. It pops one and polls it: runs the coroutine until it hits an await whose result is not ready. At that suspension, the task registers its interest — 'wake me when this socket is readable' — with the reactor, and hands back a 'not ready' signal plus a waker (a callback to re-queue it). The executor moves on to the next ready task, keeping the thread busy. Meanwhile the reactor sits on an OS readiness mechanism (epoll, kqueue, IOCP, io_uring). When the socket becomes readable, the reactor fires that task's waker, which pushes the task back onto the executor's ready queue, and next time around the executor polls it again — resuming right where it suspended. Many executors run several worker threads, each with its own ready queue, and use work-stealing to balance them.
This is the piece beginners overlook. In Rust especially, an async function returns a Future that does nothing on its own — without an executor (Tokio, async-std) polling it, the work never runs; people are baffled when their async code 'silently does nothing'. The split of duties is worth remembering: the executor decides which task runs next and on which thread (the scheduler); the reactor decides when a sleeping task is allowed to wake (the I/O watcher). Together they let a few threads multiplex thousands of concurrent tasks, each running only while it has work and yielding the moment it must wait.
Rust: an un-driven future is dead. #[tokio::main] async fn main() { spawn(task).await; } // the Tokio runtime supplies the executor (polls tasks) and the reactor (epoll-driven wakeups).
Executor = scheduler (what runs next, where); reactor = the I/O watcher that fires wakers when awaited events occur.
In Rust, a Future is lazy: nothing happens until an executor polls it. 'My async code didn't run' almost always means it was never awaited or spawned on a runtime. Some runtimes are single-threaded, others multi-threaded — choosing wrong has real consequences.