zig-skills/references/std-io.md

295 lines
7.7 KiB
Markdown

# 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?