Zig's New Async I/O
Function coloring: defeated or just shifted?
- Many argue Zig has not truly “defeated” function coloring, just changed the axis: instead of
asyncvs sync, functions are effectively “IO” vs “non-IO”. - Callers of IO-using code still need to pass an
Io(context) or arrange for one globally; once a function needs IO, its callers often need to adapt their signatures. - Defenders claim the practical impact is small: most real-world code already has access to an
Io(just like allocators), and you can stash it in app state or globals to avoid viral propagation. - Some see Zig’s approach as “color everything async-colored” by default, reducing friction versus languages where only some functions are async.
Io as context / dependency injection
- Passing
Ioexplicitly is compared to dependency injection; some call this just “parameters as DI,” others say that trivializes DI as a concept. - There’s debate over ergonomics: passing
Io(and allocator) everywhere vs hiding it in a struct, vs globalIo. Some see explicitness as very Zig-like; others fear boilerplate and “Stockholm syndrome” from allocator patterns.
Comparisons with other async models
- JS/C#/Rust-style async/await: critics highlight virality, split ecosystems (e.g., Rust+Tokio vs others), and duplicated sync/async libraries.
- Go: often cited as “color-blind” but still has subtle coloring via
contextfor cancellation. Some note you can always create a new context, which is less viral than async keywords. - Python’s asyncio: similar idea (event loop as context), but has re-entrancy and multi-loop pitfalls.
- Haskell/effects: several commenters note the similarity to an IO monad / algebraic effects, but Zig explicitly avoids making it that rigid.
Green threads, stackless coroutines, and performance
- Some worry Zig “going all in” on fibers will cap performance and complicate FFI, citing prior C++/Rust experience.
- Core contributors stress
Iois pluggable: blocking, thread-pool, green threads, and a future stackless-coroutine implementation can all back the same interface. Choice is in the application, not libraries. - Stackless coroutines are planned but not yet implemented; there are open questions about inference, UB when mixing different
Ios, and how much hidden control flow is acceptable.
Runtime polymorphism and devirtualization
- Concern: making core IO go through a vtable feels un-systems-like.
- Counterpoint: IO cost dwarfs an indirect call, and Zig’s “restricted function pointer” and whole-program compilation aim to devirtualize when only one
Ioimplementation is used. - If multiple
Ios exist, some overhead and potential code bloat tradeoffs are accepted.
Effects, purity, and capabilities
- Not passing
Iois seen as a soft signal of (approximately) pure/deterministic code, though globals and raw syscalls prevent strong guarantees. - Some see the design as an (informal) effect system; others note it’s one big “Io” effect, not a rich algebra of distinct effects.
- Capability-style uses (e.g., virtual file systems, restricted filesystem views) are possible if all code respects the
Ioabstraction, but Zig cannot enforce this at the language level.