zig-skills/SKILL.md

368 lines
13 KiB
Markdown

---
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 <duration>` 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.