/c_yield

A simple stackful coroutines implementation in C with a few demos

Primary LanguageC

Writing generators

The first thing the generator does should be calling gen_init(), storing the returned value (generator handle).

Return type of the function should be declared as gen_t *. To actually return, do gen_return(handle, val); return handle;. Never exit the generator without calling gen_return(), even if there's no important value to pass.

To yield, call gen_yield(handle, val). The call returns the value sent via gen_send(), so make sure to save it if it's important.

Calling generators

Call the generator as a usual function. It won't do anything immediately, but return the handle you will use to control it.

Interact with the generator by calling gen_send(handle, val). The value you send will be returned inside the generator from gen_yield(), whereas the value that the generator yields next time will be returned from the gen_send() call. Note: the first call of gen_send() has no corresponding gen_yield() call to return value from, so the value you pass will be silently discarded.

When the generator returns, the return value is passed as the result of gen_send() in the same way that yielded values are. Use gen_is_done() to distinguish between yielded and return values. After the generator returns, call free() on the handle.

Notes

It should work fine to pass handles around and call gen_*() on them from somewhat unexpected places (ex. yield the value from a generator from inside some other generator that was called by the first one). However, generator invocations are not reentrant, that is, you should not call gen_yield() on a handle twice without a gen_send() call on the same handle in between, and vice versa.

All gen_*() functions are thread-safe.

As gen_return() function never returns, putting return handle; after it is merely a convention, doubled as a way to silence the compiler warning about not returning a gen_t * value.

As you could see by reading the source code, gen_send() and gen_yield() are actually the same function. What it does is it dumps the state, switches to the other stack, loads the state saved there and returns the value it got passed in the first place.

We tried hard to make generators debug-friendly. In particular, we display the original generator call in the call stack, rather than the place gen_send() was called (especially useful in coroutines running on an event loop).