# std.Io - I/O API Reference (Zig 0.16.0) Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html Zig 0.16 makes I/O an explicit interface. All file, network, process, time, entropy, cancelable synchronization, and task APIs that can block or interact with the outside world need a `std.Io`. ## Ownership Rule Prefer this flow: 1. Application entry receives `init: std.process.Init`. 2. It extracts `const io = init.io`. 3. It passes `io` through setup. 4. Long-lived systems that need I/O store `io`. 5. Tests use `std.testing.io`. ```zig pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; _ = .{ gpa, io }; } ``` Temporary adapter only at a boundary: ```zig var threaded: std.Io.Threaded = .init_single_threaded; const io = threaded.io(); ``` ## Implementations - `std.Io.Threaded`: threaded, feature-complete, closest to old blocking behavior. - `std.Io.Evented`: experimental evented/M:N implementation. - `std.Io.Uring`, `std.Io.Kqueue`, `std.Io.Dispatch`: platform/proof-of-concept backends. - `std.Io.failing`: backend that supports no operations. ## Writer `std.Io.Writer` is non-generic. It contains its buffer and vtable. ### Fixed Buffer Writer ```zig var buffer: [256]u8 = undefined; var writer: std.Io.Writer = .fixed(&buffer); try writer.print("name={s} value={d}", .{ "answer", 42 }); const bytes = writer.buffered(); ``` ### File Writer ```zig const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{}); defer file.close(io); var buffer: [4096]u8 = undefined; var file_writer = file.writer(io, &buffer); const writer = &file_writer.interface; try writer.writeAll("hello\n"); try writer.print("value={d}\n", .{42}); try writer.flush(); ``` `flush` is still required for buffered file/socket writers. ### Standard Output ```zig var buffer: [4096]u8 = undefined; var stdout_writer = std.Io.File.stdout().writer(io, &buffer); const stdout = &stdout_writer.interface; try stdout.print("hello\n", .{}); try stdout.flush(); ``` ### Allocating Writer ```zig var aw: std.Io.Writer.Allocating = .init(allocator); defer aw.deinit(); try aw.writer.print("hello {s}", .{"world"}); const owned = try aw.toOwnedSlice(); defer allocator.free(owned); ``` `Allocating` has an `alignment` field in 0.16. Prefer `.init`, `.initAligned`, or `.initCapacity`. ### Discarding Writer Use this when old code used counting/discarding writer patterns. ```zig var buffer: [256]u8 = undefined; var discarding: std.Io.Writer.Discarding = .init(&buffer); try discarding.writer.print("ignored {d}", .{123}); const count = discarding.fullCount(); ``` ## Reader `std.Io.Reader` is non-generic and buffer-aware. ### Fixed Reader ```zig var reader: std.Io.Reader = .fixed("alpha\nbeta\n"); while (try reader.takeDelimiter('\n')) |line| { _ = line; } ``` `takeDelimiter` returns `!?[]u8`: `null` means EOF with no buffered bytes remaining. A final unterminated line is returned as data before a later call yields `null`. ### File Reader ```zig const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); defer file.close(io); var buffer: [4096]u8 = undefined; var file_reader = file.reader(io, &buffer); const reader = &file_reader.interface; while (try reader.takeDelimiter('\n')) |line| { _ = line; } ``` ### Read Remaining ```zig const bytes = try reader.allocRemaining(allocator, .limited(1024 * 1024)); defer allocator.free(bytes); ``` ### Binary Reads ```zig const value = try reader.takeInt(u32, .little); const header = try reader.takeStruct(Header, .little); const leb = try reader.takeLeb128(u64); ``` `takeStruct` reads an extern/packed memory representation through the reader buffer; use a layout with a defined byte representation, ensure the reader can supply the full size, and do not treat a native-layout struct as a portable wire format. ## File Integration Use `std.Io.Dir` and `std.Io.File`. ```zig const cwd = std.Io.Dir.cwd(); const contents = try cwd.readFileAlloc(io, "data.txt", allocator, .limited(1024 * 1024)); defer allocator.free(contents); try cwd.writeFile(io, .{ .sub_path = "out.txt", .data = contents, }); ``` `close`, `stat`, `setTimestamps`, readers, writers, and most operations now take `io`. ## Networking Integration Use `std.Io.net` for sockets/streams and pass `io` to high-level clients. ```zig var client: std.http.Client = .{ .allocator = allocator, .io = io, }; defer client.deinit(); ``` ## Process Integration Use `std.process.run` and `std.process.spawn`. ```zig const result = try std.process.run(allocator, io, .{ .argv = &.{ "zig", "version" }, .stdout_limit = .limited(16 * 1024), .stderr_limit = .limited(16 * 1024), }); defer allocator.free(result.stdout); defer allocator.free(result.stderr); ``` ## Entropy ```zig var bytes: [32]u8 = undefined; io.random(&bytes); const source: std.Random.IoSource = .{ .io = io }; const rng = source.interface(); ``` Use `io.randomSecure` for fresh secure entropy with error reporting. ## Time Timestamp reads now require an explicit clock choice: ```zig const now = std.Io.Timestamp.now(io, .real); const elapsed_mark = std.Io.Timestamp.now(io, .awake); ``` `Timestamp` is not a one-for-one replacement for every old `Instant`/`Timer` behavior; choose `.real` versus an appropriate monotonic clock such as `.awake`, and build elapsed-time helpers around that choice. Use a shared application helper when common timestamp reads require consistent clock selection or conversion semantics. ## Tasks and Cancelation `std.Io` provides function-level and operation-level concurrency: - `io.async(...)` - `std.Io.Group` - `std.Io.Select(U)` where `U` is the tagged union of possible results - `std.Io.Batch` - `std.Io.Queue(Elem)`, initialized with caller-provided typed element storage Cancelation guidance: - Propagate `error.Canceled` by default. - Only the code that requested cancelation should ignore it. - If handling it locally but continuing, use `io.recancel()` when cancelation should stay active. - Use `errdefer group.cancel(io)` after creating grouped tasks. ```zig var group: std.Io.Group = .init; errdefer group.cancel(io); group.async(io, worker, .{ io, arg }); try group.await(io); ``` ## Sync Primitives Blocking sync moved to `std.Io` equivalents: | Old | New | |-----|-----| | `std.Thread.Mutex` | `std.Io.Mutex` | | `std.Thread.Condition` | `std.Io.Condition` | | `std.Thread.Semaphore` | `std.Io.Semaphore` | | `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.ResetEvent` | `std.Io.Event` | | `std.Thread.WaitGroup` | Conceptually `std.Io.Group`; submit tasks, then await or cancel the group | | `std.Thread.Futex` | `io.futexWait*` / `io.futexWake`; waits have cancelable and uncancelable forms | ```zig try mutex.lock(io); defer mutex.unlock(io); ``` ```zig mutex.lockUncancelable(io); defer mutex.unlock(io); ``` ## Removed Reader/Writer Names Use these replacements: | Old | New | |-----|-----| | `std.io` | `std.Io` | | `std.Io.GenericReader` | `std.Io.Reader` | | `std.Io.AnyReader` | `std.Io.Reader` | | `std.Io.GenericWriter` | `std.Io.Writer` | | `std.Io.AnyWriter` | `std.Io.Writer` | | `std.io.fixedBufferStream(data).reader()` | `var r: std.Io.Reader = .fixed(data)` | | `std.io.fixedBufferStream(buffer).writer()` | `var w: std.Io.Writer = .fixed(buffer)` | | `std.leb.readUleb128` / `readIleb128` | `std.Io.Reader.takeLeb128` | ## Review Checklist - Does this API need an `io: std.Io` parameter? - Is `io` obtained at the application/test boundary rather than constructed deep inside? - Are all file/socket/process/time/random operations routed through `std.Io`? - Are blocking locks using `std.Io` primitives? - Are buffered writers flushed? - Are cancelation errors propagated unless this code requested cancelation? - Are temporary `Io.Threaded.init_single_threaded` adapters isolated and documented?