std.Io
0.16 overhauls I/O with the std.Io interface. everything that can block (filesystem, networking, timers, concurrency) moves through this interface.
sources:
- std library docs (JS-rendered, use browser to read)
- devlog 2025-2026 — design rationale from Andrew Kelley
- codeberg source —
lib/std/Io.zig,lib/std/Io/Threaded.zig - kristoff.it, andrewkelley.me, porting guide
the interface
like Allocator for memory, Io is passed to functions that do I/O:
fn fetchData(io: std.Io, allocator: Allocator) ![]u8 {
// io provides networking, timers, concurrency, etc.
}
this decouples code from execution model. from the devlog (Oct 2025):
Regardless of whether Io is implemented via threads, or via an event loop, this code behaves optimally. The code also works when using single-threaded, blocking Io even though the operations happen sequentially.
same code, three execution models. write once, swap backend at init.
what Io provides
- file system, networking, processes
- time and sleeping
- randomness
async,await,concurrent, andcancel- concurrent queues (
Io.Queue) - wait groups and select (
Io.Group,Io.Select) - mutexes, futexes, events, and conditions (
Io.Mutex,Io.Condition,Io.Event) - memory mapped files
backends
Io.Threaded— thread-based, always available, production defaultIo.Evented— fiber-based, experimental:- linux:
Io.Uring(io_uring) - macOS/iOS:
Io.Dispatch(GCD) - BSD:
Io.Kqueue - unsupported platforms:
void - uses userspace stack switching (fibers/green threads)
- currently experimental — known bugs (see patterns.md)
- linux:
- WASM: fiber-based backends can't work (no stack switching). stackless coroutines planned as future compiler feature.
backend selection:
const Backend = if (Io.Evented != void) Io.Evented else Io.Threaded;
init differs by backend:
// Threaded
backend = Io.Threaded.init(allocator, .{});
// Evented
try Backend.init(&backend, allocator, .{});
both expose .io() → std.Io. all downstream code uses the same Io value.
no function coloring (mostly)
async and await are library functions, not keywords. no viral async/await infection.
fn foo(io: std.Io) !void { ... }
// called normally
try foo(io);
// or asynchronously
const future = io.async(foo, .{io});
caveat: coloring shifted from keyword-based to parameter-based. if a pure computation function later needs I/O, it must accept an Io parameter, which propagates to callers. the advantage: code stays agnostic to the execution model.
files in this folder
- concurrency.md — async vs concurrent, Future, Group, Select, Queue
- synchronization.md — Mutex, Condition, CancelProtection, cancellation model
- patterns.md — backend selection, InitOptions, debug_io, long-lived tasks, timedWait workarounds