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 async vs 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 Io explicitly 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 global Io. 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 context for 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 Io is 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 Io implementation is used.
  • If multiple Ios exist, some overhead and potential code bloat tradeoffs are accepted.

Effects, purity, and capabilities

  • Not passing Io is 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 Io abstraction, but Zig cannot enforce this at the language level.