Async I/O in Zig 0.16, today

Lobsters Hottest Tools

Summary

Zig 0.16 ships a new std.Io interface for cross-platform I/O. The library zio provides a full async implementation using stackful coroutines and OS-level async APIs, enabling efficient concurrent tasks without thread-per-task overhead.

<p><a href="https://lobste.rs/s/1udl0j/async_i_o_zig_0_16_today">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 05/17/26, 03:19 AM

# Async I/O in Zig 0.16, today Source: [https://lalinsky.com/2026/05/11/async-io-in-zig-016-today.html](https://lalinsky.com/2026/05/11/async-io-in-zig-016-today.html) [Zig 0\.16](https://ziglang.org/news/0.16.0-released/)shipped last month with[`std\.Io`](https://ziglang.org/download/0.16.0/release-notes.html#IO-as-an-Interface), a cross\-platform interface for I/O and concurrency\. This is a big step for the ecosystem\. Libraries can now be written against a standard I/O abstraction, independent of the runtime, and application developers can plug in whatever implementation they want\. The only usable implementation shipped with 0\.16 is`std\.Io\.Threaded`, which uses a thread pool\. When you spawn concurrent tasks, it creates OS threads to run them\. Let’s see how it works with a simple example: ``` const std = @import("std"); const num_tasks = 10_000; fn task(io: std.Io) std.Io.Cancelable!void { try io.sleep(.fromSeconds(10), .awake); } pub fn main(init: std.process.Init) !void { var group: std.Io.Group = .init; for (0..num_tasks) |_| { try group.concurrent(init.io, task, .{init.io}); } try group.await(init.io); } ``` This spawns 10,000 concurrent tasks, each sleeping for 10 seconds\. On my machine, it completes in about 20 seconds: ``` $ time ./std_demo real 0m20.158s user 0m2.258s sys 0m10.098s ``` The overhead comes from spawning OS threads\. If you try increasing this to 50,000 tasks, it will likely fail on most systems due to thread limits \(`ulimit \-u`on Linux\)\. This isn’t just an arbitrary benchmark\. Asynchronous I/O exists to solve a real problem: network servers with many connected clients\. You don’t want to spawn an OS thread for every client connection\. That’s why we have event loops, coroutines, and async I/O\. There is`std\.Io\.Evented`in the standard library, which is meant to use io\_uring on Linux and kqueue on BSD/macOS\. It’s still a work in progress though, missing many functions and doesn’t currently compile\. I’ve written about[zio](https://github.com/lalinsky/zio)[before](https://lalinsky.com/2025/10/26/zio-async-io-for-zig.html), and I’ve just released version 0\.11 with a full`std\.Io`implementation\. It uses stackful coroutines and asynchronous OS\-level I/O APIs \(io\_uring or epoll on Linux, kqueue on BSD/macOS, IOCP on Windows\)\. Here’s the same example using zio: ``` const std = @import("std"); const zio = @import("zio"); const num_tasks = 10_000; fn task(io: std.Io) std.Io.Cancelable!void { try io.sleep(.fromSeconds(10), .awake); } pub fn main(init: std.process.Init) !void { const rt = try zio.Runtime.init(init.gpa, .{}); defer rt.deinit(); const io = rt.io(); var group: std.Io.Group = .init; for (0..num_tasks) |_| { try group.concurrent(io, task, .{io}); } try group.await(io); } ``` The code is almost identical\. You just initialize a zio runtime and use its`io\(\)`method to get the`std\.Io`interface\. With zio, the same 10,000 tasks complete in about 10 seconds: ``` $ time ./zio_demo real 0m10.606s user 0m3.136s sys 0m7.126s ``` That’s the expected time, since all tasks run truly concurrently\. You can increase this to 50,000 or more tasks and it will continue to work, limited only by available memory\. You can use this`io`instance for anything you’d use`std\.Io\.Threaded`for\. To write an HTTP server with`std\.http\.Server`, for example, just pass zio’s`io`and it will work the same way\. If you want to use async I/O in Zig 0\.16 with the standard APIs, you don’t need to wait for`std\.Io\.Evented`to be ready\. Zio’s implementation is still new, so if you hit any problems, please reach out on[GitHub](https://github.com/lalinsky/zio)and I’ll be happy to help\.

Similar Articles

Zig's Io.Threaded is Neat

Lobsters Hottest

Article discusses Zig's std.Io.Threaded, an implementation of Zig's Io interface that uses blocking syscalls and supports cancellation via signals, contrasting concurrency and parallelism.

Writergate: Zig I/O Interface Overhaul

Hacker News Top

Writergate is the informal name for Zig's I/O interface overhaul, which replaces generic types with concrete types and vtables to address design flaws and enable features like async I/O. The changes are breaking and require updates to existing code.

Zig 0.16.0 release notes: "Juicy Main"

Simon Willison's Blog

Zig 0.16.0 released with a new feature called 'Juicy Main' that provides dependency injection for the main() function, giving convenient access to allocators, IO, environment variables, and CLI arguments.

Thread-identity switcheroo for io_uring

Lobsters Hottest

The article presents an RFC patch set by Jens Axboe for io_uring that proposes handling potentially blocking operations without context switching to improve performance by reducing overhead.