Zig 0.16.0 async I/O programming with the new std.Io interface. Covers async/concurrent primitives, Future handling, cancellation patterns, and I/O implementation strategies...
Zig 0.16.0 introduces a redesigned async I/O system based on the std.Io interface. Unlike the old async/await (removed in 0.11), the new design decouples concurrency expression from execution models, allowing code to work optimally across synchronous, multi-threaded, and event-driven contexts.
Critical Concept: Asynchrony ≠Concurrency
async: Operations can proceed out-of-order (sequential awaiting is valid)concurrent: Operations must proceed simultaneously (requires parallelism)Fundamentals:
references/async-overview.md - New async I/O design philosophyreferences/io-interface.md - std.Io interface and primitivesreferences/async-vs-concurrent.md - Critical distinction explainedPatterns:
references/future-handling.md - Future.await() and Future.cancel()references/resource-management.md - Defer patterns for async resourcesreferences/io-implementations.md - Blocking, ThreadPool, EventLoop, StacklessAdvanced:
references/message-passing.md - Io.Queue for synchronizationreferences/vectorized-io.md - sendFile() and drain() operationsreferences/migration-guide.md - From old async or sync codeComplete async I/O demonstrations:
examples/basic_async.zig - Simple async operations with io.async()examples/concurrent_tasks.zig - True concurrency with io.concurrent()examples/file_operations.zig - Async file I/O patternsexamples/http_server.zig - Async HTTP server with event loopexamples/producer_consumer.zig - Message passing with Io.Queueexamples/cancellation.zig - Proper task cancellation patternsStarting points for async code:
assets/templates/async-function.zig - Async function templateassets/templates/async-server.zig - Async server templateassets/templates/async-client.zig - Async client templateassets/templates/threaded-io.zig - Thread pool I/O setupconst Io = struct {
/// Spawn async work (may execute immediately or be scheduled)
fn async(self: *Io, func: anytype, args: anytype) Future
/// Spawn concurrent work (fails if parallelism unavailable)
fn concurrent(self: *Io, func: anytype, args: anytype) !Future
/// Message passing primitive
fn Queue(comptime T: type) type
};
const Future = struct {
/// Wait for result (idempotent)
fn await(self: *Future, io: *Io) !T
/// Cancel and retrieve result (idempotent)
fn cancel(self: *Future, io: *Io) !T
};
std.Io.Threaded multiplexes across OS threadsio_uring/kqueue with green threads (future)io: *std.Io like allocatorsio.async() for potentially concurrent operationsfuture.await(io) when neededdefer future.cancel(io) for cleanupio.concurrent() when simultaneous execution requiredUse io.async() when:
Use io.concurrent() when:
var future = io.async(operation, .{io, args});
defer if (future.cancel(io)) |result| {
cleanup(result);
} else |_| {};
// Use future...
try future.await(io);
This single pattern handles both success and failure cases.
fn saveFiles(io: *std.Io, data: []const u8) !void {
var fut_a = io.async(saveFile, .{io, data, "a.txt"});
var fut_b = io.async(saveFile, .{io, data, "b.txt"});
try fut_a.await(io);
try fut_b.await(io);
}
var queue = io.Queue(Task).init();
// Producer
var producer = try io.concurrent(produce, .{io, &queue});
// Consumer
var consumer = try io.concurrent(consume, .{io, &queue});
try producer.await(io);
try consumer.await(io);
var work = io.async(longOperation, .{io});
var timeout = io.async(sleep, .{io, 5000});
const result = io.race(&.{work, timeout});
if (result == 1) { // timeout won
_ = work.cancel(io) catch {};
return error.Timeout;
}
pub fn processData(io: *std.Io, allocator: Allocator, data: []const u8) !void {
// Use io just like allocator
var future = io.async(helper, .{io, allocator, data});
try future.await(io);
}
Old Model (0.10.x and earlier):
async and await keywordsNew Model (0.16.0+):
io.async() and future.await(io) methodsFrom synchronous code:
// Before (sync)
try saveFile(data, "a.txt");
try saveFile(data, "b.txt");
// After (async)
var fut_a = io.async(saveFile, .{io, data, "a.txt"});
var fut_b = io.async(saveFile, .{io, data, "b.txt"});
try fut_a.await(io);
try fut_b.await(io);
From old async/await:
// Before (old async - removed)
var frame_a = async saveFile(data, "a.txt");
var frame_b = async saveFile(data, "b.txt");
try await frame_a;
try await frame_b;
// After (new async)
var fut_a = io.async(saveFile, .{io, data, "a.txt"});
var fut_b = io.async(saveFile, .{io, data, "b.txt"});
try fut_a.await(io);
try fut_b.await(io);
io.async() unless concurrent truly neededMinimum Zig Version: 0.16.0 (unreleased as of writing)
This skill targets the new async I/O design planned for Zig 0.16.0 based on:
Status: Implementation in progress, API subject to change
Load references for deep dives:
references/async-overview.md - Complete design philosophyreferences/io-implementations.md - Implementation strategiesreferences/message-passing.md - Advanced synchronization