0.16 Io Summary

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:

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, and cancel
  • 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 default
  • Io.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)
  • 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