--- name: zig description: Up-to-date Zig programming language patterns for version 0.16.0. Use when writing, reviewing, or debugging Zig code, working with build.zig/build.zig.zon, translating C headers, or using comptime metaprogramming. Critical for avoiding outdated patterns from training data, especially std.Io ownership, Juicy Main, std.Io.Dir/File/net/process APIs, Io synchronization primitives, removed @Type, @cImport migration, unmanaged containers, and removed std.Thread.Pool. --- # Zig Language Reference (v0.16.0) Zig evolves rapidly. Training data contains outdated patterns that cause compilation errors or subtle runtime bugs. This skill documents modern Zig 0.16.0 patterns and points to deeper references when needed. Always load **[Zig 0.16 Release Notes Migration Reference](references/zig-0.16-release-notes.md)** for upgrade work, stdlib API questions, C translation changes, I/O changes, or code review involving old Zig examples. It is grounded in the official Zig 0.16.0 release notes: https://ziglang.org/download/0.16.0/release-notes.html ## Critical: I/O Is an Interface Starting in Zig 0.16.0, input/output APIs require a `std.Io` instance. Treat anything that can block, touch the OS, use entropy/time/network/process/file APIs, or introduce nondeterminism as owned by `std.Io`. Preferred app entry: ```zig const std = @import("std"); pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); _ = .{ gpa, io, args }; } ``` Preferred test I/O: ```zig const io = std.testing.io; ``` Temporary adapter only when a boundary cannot yet receive `io`: ```zig var threaded: std.Io.Threaded = .init_single_threaded; const io = threaded.io(); ``` General rule: - Accept `io: std.Io` where the API performs or later owns I/O. - Store `io` on long-lived systems, allocators, registries, timers, queues, and clients that need it after initialization. - Do not create local fallback `Io.Threaded` instances deep in library code unless explicitly approved as a transition shim. - When converting existing code, prefer threading `io` through signatures over global state. ## Critical: File, Net, Process, Time, Random ### Files Use `std.Io.Dir` and `std.Io.File`; old `std.fs.File`/`std.fs.Dir` patterns are obsolete. ```zig const cwd = std.Io.Dir.cwd(); const file = try cwd.openFile(io, "data.txt", .{}); defer file.close(io); var buf: [4096]u8 = undefined; var reader = file.reader(io, &buf); const bytes = try reader.interface.allocRemaining(gpa, .limited(1024 * 1024)); defer gpa.free(bytes); ``` ```zig const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{}); defer file.close(io); var buf: [4096]u8 = undefined; var writer = file.writer(io, &buf); try writer.interface.print("hello {s}\n", .{"world"}); try writer.interface.flush(); ``` Convenience: ```zig const bytes = try std.Io.Dir.cwd().readFileAlloc(io, "data.txt", gpa, .limited(max_size)); defer gpa.free(bytes); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = "out.txt", .data = "hello\n", }); ``` ### Reader and Writer `std.io` is replaced by `std.Io`. `GenericReader`, `AnyReader`, `GenericWriter`, `AnyWriter`, and `FixedBufferStream` are gone. ```zig var r: std.Io.Reader = .fixed("a\nb\n"); while (try r.takeDelimiter('\n')) |line| { _ = line; } var out_buf: [256]u8 = undefined; var w: std.Io.Writer = .fixed(&out_buf); try w.print("value={d}", .{42}); const written = w.buffered(); ``` Custom format methods use the writer-only signature: ```zig pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.print("{s}:{d}", .{ self.name, self.value }); } ``` Use `{f}` to invoke format methods. ### Process Use Juicy Main for args/env and `std.process.run` / `std.process.spawn` for child processes. ```zig const result = try std.process.run(gpa, io, .{ .argv = &.{ "git", "status", "--short" }, .stdout_limit = .limited(64 * 1024), .stderr_limit = .limited(64 * 1024), }); defer gpa.free(result.stdout); defer gpa.free(result.stderr); ``` ```zig var child = try std.process.spawn(io, .{ .argv = &.{ "tool", "--flag" }, .stdout = .pipe, .stderr = .pipe, }); defer child.kill(io); const term = try child.wait(io); _ = term; ``` Current directory: ```zig const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); ``` ### Networking and HTTP Use `std.Io.net` and pass `io` to high-level clients. ```zig var client: std.http.Client = .{ .allocator = gpa, .io = io, }; defer client.deinit(); ``` ### Time 0.16 routes time through `std.Io` types: - `std.time.Instant` -> `std.Io.Timestamp` - `std.time.Timer` -> `std.Io.Timestamp` - `std.time.timestamp` -> `std.Io.Timestamp.now` Applications may centralize timestamp reads behind a shared helper so clock selection and timestamp semantics remain consistent. ### Random and Entropy Do not use `std.crypto.random` directly in new 0.16 code. ```zig var seed: [32]u8 = undefined; io.random(&seed); const source: std.Random.IoSource = .{ .io = io }; const rng = source.interface(); ``` ## Critical: I/O Synchronization Blocking synchronization moved from `std.Thread` to `std.Io` so it cooperates with the runtime backend and cancelation. Migration map: - `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` -> `std.Io.Group` - `std.Thread.Futex` -> `std.Io.Futex` Cancelable lock: ```zig try mutex.lock(io); defer mutex.unlock(io); ``` Uncancelable lock for short critical sections or cleanup: ```zig mutex.lockUncancelable(io); defer mutex.unlock(io); ``` Do not replace these with custom spin loops. Use std primitives unless a measured, documented special case requires otherwise. ## Critical: Language and Builtins ### Removed or Deprecated - `@Type` removed. Use `@Int`, `@Struct`, `@Union`, `@Enum`, `@Pointer`, `@Fn`, `@Tuple`, `@Opaque`, or `@EnumLiteral`. - `@cImport` is deprecated long-term. Prefer `b.addTranslateC(...)` and import `translate_c.createModule()`. - `async`/`await` keywords remain removed; use `std.Io` task APIs, an application scheduler, or explicit threads. - `usingnamespace` is removed; explicitly re-export names. - `@fence` is removed; use stronger atomic orderings or RMW operations. - `@intFromFloat` is deprecated; use `@trunc` when truncating float to integer. ### Packed and Extern Layout - Packed structs/unions cannot contain pointers. - Packed unions need unambiguous bit sizes and may use explicit backing integers. - Extern enum/packed types need explicit backing types. - ABI-sensitive bindings must keep comptime size/alignment/offset assertions. ### Vectors and Arrays - Runtime vector indexes are forbidden. - Vectors and arrays no longer support in-memory coercion; convert explicitly. ### Safer Pointer Returns The compiler rejects trivially returning the address of an expired local. When returning pointers, prove ownership and lifetime explicitly. ## Critical: Containers and Allocators Use `.empty` for empty unmanaged containers and pass allocators to methods. ```zig var list: std.ArrayList(u32) = .empty; defer list.deinit(gpa); try list.append(gpa, 42); ``` 0.16 container changes: - `std.ArrayHashMap`, `std.AutoArrayHashMap`, and `std.StringArrayHashMap` removed. - Use `std.array_hash_map.Custom`, `std.array_hash_map.Auto`, and `std.array_hash_map.String`. - `std.PriorityQueue` and `std.PriorityDequeue` no longer store allocators. - Priority queues use `push`/`pop` terminology and `.empty` initialization. - `std.SegmentedList` removed. - `std.heap.ThreadSafe` removed. - `std.heap.ArenaAllocator` is thread-safe and lock-free. ## Critical: Build System The 0.15 root-module build style remains the baseline: ```zig const exe = b.addExecutable(.{ .name = "app", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); ``` Use module-level APIs: ```zig exe.root_module.addImport("helper", helper_mod); exe.root_module.linkSystemLibrary("SDL3", .{}); exe.root_module.addCSourceFiles(.{ .files = &.{"src/foo.c"} }); ``` 0.16 additions and removals: - `--prominent-compile-errors` removed; use `--error-style minimal`. - New `--multiline-errors` styles are available. - `zig build test --test-timeout ` can bound test runtime. - `Build.makeTempPath` and RemoveDir step removed. - Use `b.addTempFiles`, `b.addMutateFiles`, `b.tmpPath`, and WriteFile temp/mutate modes. - Use build-system `addTranslateC` for new translated C bindings. ## Critical: C Translation and ABI Safety Translate-c is now Aro/translate-c based rather than libclang based. It is intended to be non-breaking, but subtle generated-code changes can happen. For ABI-sensitive C bindings: - Keep translated output under review. - Preserve size/alignment/offset comptime asserts. - Compare enum values, bit flags, packed layouts, extern structs, calling conventions, and macro translations. - Do not dismiss translate-c diffs as cosmetic until ABI checks pass. ## Application Integration Guidance When integrating these patterns into an application: - Prefer an explicit `std.Io` parameter; an application-owned accessor is also reasonable where the architecture already provides one. - New file operations require an `io` parameter at the API boundary. - Long-lived allocators/registries/queues that lock or perform I/O should accept `io` at init and expose it if needed. - Prefer a shared application helper for common wall-clock timestamps. - Keep dynamic-library behavior behind a platform abstraction, especially on Windows where `std.DynLib` support changed. ## Import Loop Guidance Do not over-index on dependency loops when importing source files directly. - `@import("mything.zig")` is a direct file import and should be treated as safe. - Only consider import-loop risk when importing a module name such as `@import("module")`. - A top-level file plus same-named helper directory is normal: ```zig // module.zig const moduleComponent = @import("module/moduleComponent.zig"); // module/moduleComponent.zig const module = @import("../module.zig"); ``` ## Quick Fixes | Symptom | Zig 0.16 Fix | |---------|--------------| | missing `std.fs.File` / `std.fs.Dir` | Use `std.Io.File` / `std.Io.Dir` and pass `io` | | file `close()` takes an argument | Use `file.close(io)` or `dir.close(io)` | | old stdout writer fails | `std.Io.File.stdout().writer(io, &buf)` | | old `std.process.Child.run` fails | Use `std.process.run(gpa, io, .{ ... })` | | old `Child.init` fails | Use `std.process.spawn(io, .{ ... })` | | old `std.crypto.random` fails | Use `io.random` or `std.Random.IoSource` | | old `std.Thread.Mutex.lock()` fails in I/O code | Use `std.Io.Mutex` and `lock(io)` / `lockUncancelable(io)` | | `std.Thread.Pool` missing | Use an application scheduler or carefully designed `std.Io.Group` / `io.async` | | `@Type` missing | Use specific type-constructor builtins | | `@cImport` warning | Move translation to `b.addTranslateC(...)` | | `std.mem.indexOf` not found | Use `std.mem.find` / `findScalar` / `cut` helpers | | `{D}` duration format fails | Format `std.Io.Duration` with `{f}` | | `std.fmt.Formatter` missing | Use `std.fmt.Alt` | | `std.fmt.format` missing | Use `std.Io.Writer.print` | | `std.fs.path.relative` signature changed | Pass cwd path and optional environment map | | `stat.atime` type changed | Handle optional access time | ## References Load these selectively: - **[Zig 0.16 Release Notes Migration Reference](references/zig-0.16-release-notes.md)** - First stop for version-specific upgrade work, organized by official release-note section. - **[std.Io](references/std-io.md)** - 0.16 reader/writer, file I/O, `Io` ownership, sync and task concepts. - **[std.fs / std.Io.Dir](references/std-fs.md)** - File system migration and common 0.16 patterns. - **[std.process](references/std-process.md)** - Juicy Main, args/env, child process APIs, current directory. - **[std.Thread / std.Io sync](references/std-thread.md)** - Thread spawning plus migration to `std.Io` synchronization primitives. - **[std.time / std.Io time](references/std-time.md)** - Timestamp migration and duration handling. - **[std.Build](references/std-build.md)** - Build graph, modules, package deps, 0.16 build-system changes. - **[Built-in Functions](references/builtins.md)** - Builtin syntax and 0.16 `@Type` replacement. - **[Comptime Reference](references/comptime.md)** - Type reflection, type construction, lazy type resolution, compile-time patterns. - **[C Interop](references/c-interop.md)** - C ABI, exports, translated headers, and binding checks. - **[Zig Patterns](references/patterns.md)** - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict. - **[Code Review](references/code-review.md)** - Review checklist. Always apply the 0.16 release-note checks first. General references remain available for specific modules: `std.mem`, `std.fmt`, `std.json`, `std.zon`, `std.crypto`, `std.http`, `std.net`, `std.compress`, `std.tar`, `std.zip`, `std.testing`, `std.debug`, `std.log`, `std.atomic`, `std.simd`, and other files under `references/`. If any reference still contains a historical 0.14/0.15 example, the Zig 0.16 release-notes reference and the top-level rules in this file win.