Zig 0.16.0 API guidance and porting notes. Use this when writing or upgrading Zig code to the 0.16.0 stable release (std.Io era, @Type removal, @cImport deprecation).
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Zig 0.16.0 API guidance and porting notes. Use this when writing or upgrading Zig code to the 0.16.0 stable release (std.Io era, @Type removal, @cImport deprecation).
Zig 0.16.0 is a major release introducing std.Io as the unified I/O interface, removing @Type, deprecating @cImport, and significantly reworking the build system package management.
Local Documentation First
Run zig env to discover installation paths — never hardcode them. Key fields: .lib_dir, .std_dir, .version.
Language Reference:
<lib_dir>/../doc/langref.html
Std Library Source: read files under .std_dir for API verification.
Std Library Docs: run zig std to start a local HTTP server.
New: std.Io.net.Socket.createPair for socketpair usage in tests.
I/O Readers/Writers
Writer/reader interfaces live under std.Io. TLS expects *std.Io.Reader / *std.Io.Writer (struct fields .interface). Pass pointers to interfaces, not call interface() as a function.
std.time.sleep removed; use std.Io.sleep(io, Duration, Clock).
std.time.milliTimestamp removed. Use std.time.Timer or std.Io.Clock.now(clock, io) and compare Timestamp.nanoseconds.
Random secure bytes: std.Io.randomSecure(io, buf); no std.crypto.random or std.posix.getrandom convenience.
std.process.getEnvVarOwned removed; use std.c.getenv and copy.
std.posix.exit removed; use std.process.exit.
TLS Client Options
std.crypto.tls.Client.Options requires entropy: *const [entropy_len]u8 and realtime_now_seconds: i64. Fill entropy with std.Io.randomSecure; compute seconds with Io.Clock.now(.real, io) / ns_per_s.
MemoryPool API Changes
std.heap.MemoryPool(T).initCapacity(allocator, n) returns the pool.
create/destroy now require allocator. No bare init() or zero-arg deinit().
Format Options
std.fmt.Options replaces FormatOptions.
std.fmt.format helper removed; call writer.print directly (writers live in std.Io.Writer).
std.fmt.Formatter renamed to Alt.
std.fmt.bufPrintZ renamed to std.fmt.bufPrintSentinel.
Randomness / Crypto
std.crypto.random removed. Use an std.Io instance: const io = std.Io.Threaded.global_single_threaded.ioBasic(); io.random(&buf);.
Ed25519.KeyPair.generate now requires an io: std.Io argument.
Enum Conversion
std.meta.intToEnum removed. Use std.enums.fromInt(EnumType, value) (returns ?EnumType).
meta.declList removed.
Fixed-Buffer Writers in Tests
std.io.fixedBufferStream removed. For in-memory writes use var w = std.Io.Writer.fixed(buf); and read bytes with std.Io.Writer.buffered(&w).
std.ArrayList no longer has .init(allocator) shorthand; use .initCapacity(allocator, n).
componentIterator/ComponentIterator.init can no longer fail
Current Directory API renamed
// OLD
std.process.getCwd(buffer)
std.process.getCwdAlloc(allocator)
// NEW
std.process.currentPath(io, buffer)
std.process.currentPathAlloc(io, allocator)
Preopens & Atomic / Temporary Files
Preopens
// OLD (WASI)
const wasi_preopens: std.fs.wasi.Preopens = try .preopensAlloc(arena);
// NEW
const preopens: std.process.Preopens = try .init(arena);
// Or simply use init.preopens from Juicy Main.
Preopens is void on non-WASI targets — zero-cost abstraction.
Atomic / Temporary Files
std.Io.File.Atomic is the new API for atomic file writes and temporary files.
Linux: integrates with O_TMPFILE when possible.
New: std.Io.File.hardLink
Temporary/random filenames are generated via the Io vtable instead of std.crypto.random.
Memory & Allocator Changes
ArenaAllocator is now lock-free and thread-safe
std.heap.ArenaAllocator no longer needs ThreadSafeAllocator wrapping. Performance is comparable single-threaded and faster under contention (~7 threads).
ThreadSafe Allocator removed
std.heap.ThreadSafe is removed; use ArenaAllocator directly, or synchronize access manually.
var map = std.StringHashMap(u32).empty;
defer map.deinit(gpa);
try map.put(gpa, "key", 42);
const val = map.get("key") orelse 0;
stdout / stderr with Juicy Main
pub fn main(init: std.process.Init) !void {
const io = init.io;
// Direct streaming write
try std.Io.File.stdout().writeStreamingAll(io, "Hello, world!\n");
// Or via writer interface
var stdout_writer = std.Io.File.stdout().writer(&.{});
try stdout_writer.interface.print("value: {d}\n", .{42});
}
Fixed-Buffer Reader / Writer
// Reader from byte slice
var data = "line1\nline2\n";
var reader: std.Io.Reader = .fixed(data);
// Writer into a stack buffer
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writer.interface.print("count: {d}", .{7});
Any dependency with matching name + fingerprint will resolve to the local path instead of being fetched. This is ephemeral — remove the flag to revert.
Packages Fetched to zig-pkg Directory
Dependencies are now fetched into a local zig-pkg/ directory (next to build.zig) instead of the global cache. After filtering, they are recompressed into the global cache for future reuse.
Unit Test Timeouts
Specify per-test timeouts:
zig build test --test-timeout 500ms
After the timeout, the test process is killed and restarted for the next test.
ConfigHeader
std.Build.Step.ConfigHeader now handles leading whitespace for cmake-style config headers correctly.
Compiler / Toolchain
C Translation
@cImport still exists but is deprecated and now backed by arocc instead of libclang.
For new code, always use addTranslateC in build.zig.
Type Resolution
Reworked internal type resolution. Most previously-working code still works, and some "dependency loop" errors are now resolved.
Self-referential alignment queries (e.g. align(@alignOf(@This()))) now correctly error.
LLVM Backend
Experimental incremental compilation support.
Error set types now lowered as enums in debug info, so error names are visible at runtime.
Target Support
Notable additions:
aarch64-freebsd, aarch64-netbsd, loongarch64-linux, powerpc64le-linux, s390x-linux, x86_64-freebsd, x86_64-netbsd, x86_64-openbsd now tested in CI.
Basic support added for Alpha, KVX, MicroBlaze, OpenRISC, PA-RISC, SuperH.
Solaris, AIX, z/OS support removed.
Stack tracing improved across almost all major targets.
Testing Adjustments
For in-process client/server tests, use std.Io.net.Socket.createPair or raw socketpair(AF.UNIX, SOCK.STREAM|CLOEXEC, 0, &fds) to avoid relying on Io vtable network (Threaded nets return NetworkDown if not wired).
Error sets tightened in many std.Io functions — remove unreachable branches accordingly.
Porting Strategy (0.15 → 0.16)
Replace @Type calls with the specific new builtin functions.
Replace @cImport with addTranslateC in build.zig.
Add fingerprint and fix name in build.zig.zon.
Thread std.Io through your app — any function doing I/O, sleep, random, or time needs an io parameter.
Update std.net usages to std.Io.net or raw syscalls.
Update ArrayList calls to pass allocator explicitly and use initCapacity.
Fix error set names (CrossDevice, FileBusy, EnvironmentVariableMissing, DirNotEmpty).
Run zig build test --test-timeout 500ms to catch hanging tests early.
Use this skill when writing new Zig 0.16.0 code or upgrading from 0.15.x.