2026-06-09 · 7 min · c · coroutines · systems · explainer
Some functions want to be callers. Some want to be callees. The trouble starts when two pieces of code both want to be the caller.
Picture a decompressor that walks a byte stream and emits one character at a time, and a parser that consumes characters one at a time. Each is most natural as a loop that drives the other:
Whichever one you make a callee, you have to turn inside-out: rip out its loop,
hoist its locals into static state, and reconstruct "where was I?" by hand every
time it's called. The algorithm disappears into a state machine.
A coroutine is the escape hatch: a function you can return from in the middle
and later resume exactly where it left off, locals and loop position intact. C
doesn't have them. But — as Simon Tatham showed in his
classic note — you
can fake them with a switch statement and one preprocessor macro.
The painful version first
Here's that decompressor rewritten as a callee the honest way — a hand-rolled state machine. It works, and it's miserable:
int decompressor(void) {
static int state = 0, len, c;
switch (state) {
case 0: /* fresh start */
while (1) {
c = getchar();
if (c == EOF) return EOF;
if (c == 0xFF) { /* run-length escape */
len = getchar();
c = getchar();
while (len--) {
state = 1; return c; /* <-- emit, remember we're here */
case 1: ; /* <-- ...come back to here */
}
} else {
state = 2; return c;
case 2: ;
}
}
}
}Every return needs a unique number, a matching case, and an assignment to
state. Add a branch and you renumber everything. The bookkeeping is the bug
surface.
The insight: let __LINE__ be the state
The numbers are pure noise. We never read them — we only need each return to
have a label unique to its position, and a way to jump back to it. The C preprocessor
already hands out a unique number per position: __LINE__.
So: on the way out, save __LINE__. On the way back in, switch on the saved value
and let a case __LINE__: right after the return catch it. Two macros:
#define crBegin static int state = 0; switch (state) { case 0:
#define crReturn(x) do { state = __LINE__; return x; \
case __LINE__: ; } while (0)
#define crFinish }That's the entire idea. crBegin opens a switch on the saved state. crReturn
stamps the current line into state, returns, and drops a case label at that exact
line so the next call resumes one statement later. crFinish closes the brace.
Watch it run
A three-value generator — next() returns 0, 1, 2, then -1 — makes the control flow
visible. Step through it: watch state get stamped with a line number on the way out,
and the switch teleport straight back into the middle of the for loop on the way
back in.
1▸int next(void) {2▸ static int state = 0, i;3▸ switch (state) {4▸ case 0:5▸ for (i = 0; i < 3; i++) {6▸ state = __LINE__; return i;7▸ case __LINE__: ;8▸ }9▸ }10▸ return -1;11▸}
The magic moment is the jump from switch (state) to case __LINE__: inside the
loop. The function never "starts over" — it lands back exactly where it returned, with
i right where it was.
How the macros expand
It reads like ordinary code, but here's what the preprocessor actually produces, one layer at a time:
You write the coroutine in its natural, loop-shaped form:
Where it bites
This is a beautiful hack, and like every beautiful hack it has sharp edges. Tatham is candid about them, and you should be too:
The static rule hides a worse problem: static means one shared instance. Two
callers can't run the same coroutine independently — they'd stomp each other's state
and i. Fine for a single global decompressor; fatal for anything reentrant or
threaded.
Making it reentrant
The fix is to stop using static and instead thread all the state through a context
struct the caller owns. Every "serious" local becomes a field; the macros read and
write ctx->state instead of a file-scoped one:
struct coro {
int state;
int i, len, c; /* everything that must survive a yield */
};
#define crBegin(ctx) switch ((ctx)->state) { case 0:
#define crReturn(ctx, x) do { (ctx)->state = __LINE__; return x; \
case __LINE__: ; } while (0)
#define crFinish }
int next(struct coro *ctx) {
crBegin(ctx);
for (ctx->i = 0; ctx->i < 3; ctx->i++)
crReturn(ctx, ctx->i);
crFinish;
return -1;
}Now each caller allocates its own struct coro, and you can run a hundred independent
generators at once. The price is cosmetic — ctx->i everywhere you'd have written
i — and Tatham's own verdict is the honest one: "virtually all your serious
variables become elements of the coroutine context structure." You trade a little
syntax for reentrancy. Usually worth it.
Why this matters beyond the trick
You don't reach for these macros often — real codebases use explicit state machines,
threads, or a language with async/yield built in. But the idea underneath is worth
keeping: a coroutine is just a state machine where the compiler tracks the state for
you. async/await in Rust, generators in Python, goroutines parked on a channel —
all of them are, at bottom, "save where I am, return, resume later." Tatham's macro is
that idea stripped to its absolute minimum: one switch, one __LINE__, and the
nerve to put a case label inside a loop.
Built on Simon Tatham's Coroutines in C (2000) — still the clearest thing ever written on the subject.