14 KiB
| name | description |
|---|---|
| zig | 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 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
Zig 0.16.0 routes many new blocking, OS-facing, and nondeterministic APIs
through a std.Io instance. For new APIs, treat entropy, time, networking,
process, and file operations as std.Io responsibilities. Existing stdlib APIs
are not uniformly parameterized this way, so check each installed signature.
Preferred app entry:
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:
const io = std.testing.io;
Temporary adapter only when a boundary cannot yet receive io:
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
General rule:
- Accept
io: std.Iowhere the API performs or later owns I/O. - Store
ioon long-lived systems, allocators, registries, timers, queues, and clients that need it after initialization. - Do not create local fallback
Io.Threadedinstances deep in library code unless explicitly approved as a transition shim. - When converting existing code, prefer threading
iothrough 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.
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);
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:
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.
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:
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.
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);
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:
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.
var client: std.http.Client = .{
.allocator = gpa,
.io = io,
};
defer client.deinit();
Time
0.16 routes runtime-dependent time through std.Io. Choose an explicit
std.Io.Clock and pass io; for example, use
std.Io.Timestamp.now(io, .real) for wall time and .boot or .awake for
elapsed-time measurements. std.Io.Timestamp is not a one-for-one timer
replacement: preserve the old callsite's clock semantics during migration.
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.
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.Mutexstd.Thread.Condition->std.Io.Conditionstd.Thread.Semaphore->std.Io.Semaphorestd.Thread.RwLock->std.Io.RwLockstd.Thread.ResetEvent->std.Io.Eventstd.Thread.WaitGroup->std.Io.Groupstd.Thread.Futexoperations ->io.futexWait,io.futexWaitTimeout,io.futexWaitUncancelable, andio.futexWakeon a suitably aligned four-byte value
Cancelable lock:
try mutex.lock(io);
defer mutex.unlock(io);
Uncancelable lock for short critical sections or cleanup:
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
@Typeremoved. Use@Int,@Struct,@Union,@Enum,@Pointer,@Fn,@Tuple, or@EnumLiteral. Declare opaque types withopaque {}.@cImportis deprecated long-term. Preferb.addTranslateC(...)and importtranslate_c.createModule().async/awaitkeywords remain removed; usestd.Iotask APIs, an application scheduler, or explicit threads.usingnamespaceis removed; explicitly re-export names.@fenceis removed; use stronger atomic orderings or RMW operations.@intFromFloatconverts a finite, in-range float to an integer.@trunctruncates a floating value while retaining a floating result type.
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.
var list: std.ArrayList(u32) = .empty;
defer list.deinit(gpa);
try list.append(gpa, 42);
0.16 container changes:
std.ArrayHashMap,std.AutoArrayHashMap, andstd.StringArrayHashMapremoved.- Use
std.array_hash_map.Custom,std.array_hash_map.Auto, andstd.array_hash_map.String. std.PriorityQueueandstd.PriorityDequeueno longer store allocators.- Priority queues use
push/popterminology and.emptyinitialization. std.SegmentedListremoved.std.heap.ThreadSaferemoved.std.heap.ArenaAllocator's allocator interface is thread-safe when its child allocator is thread-safe. Do not assume a universal lock-free guarantee.
Critical: Build System
The 0.15 root-module build style remains the baseline:
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:
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-errorsremoved; use--error-style minimal.- New
--multiline-errorsstyles are available. zig build test --test-timeout <duration>can bound test runtime.Build.makeTempPathand RemoveDir step removed.- Use
b.addTempFiles,b.addMutateFiles,b.tmpPath, and WriteFile temp/mutate modes. - Use build-system
addTranslateCfor 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.Ioparameter; an application-owned accessor is also reasonable where the architecture already provides one. - New file operations require an
ioparameter at the API boundary. - Long-lived allocators/registries/queues that lock or perform I/O should accept
ioat 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.DynLibsupport 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:
// 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 - First stop for version-specific upgrade work, organized by official release-note section.
- std.Io - 0.16 reader/writer, file I/O,
Ioownership, sync and task concepts. - std.fs / std.Io.Dir - File system migration and common 0.16 patterns.
- std.process - Juicy Main, args/env, child process APIs, current directory.
- std.Thread / std.Io sync - Thread spawning plus migration to
std.Iosynchronization primitives. - std.time / std.Io time - Timestamp migration and duration handling.
- std.Build - Build graph, modules, package deps, 0.16 build-system changes.
- Built-in Functions - Builtin syntax and 0.16
@Typereplacement. - Comptime Reference - Type reflection, type construction, lazy type resolution, compile-time patterns.
- C Interop - C ABI, exports, translated headers, and binding checks.
- Zig Patterns - Idiomatic patterns. Treat 0.16 release-note reference as authoritative when examples conflict.
- Code Review - 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.Io.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.