About zig-0.16
zig-0.16 is a programming guide skill for developers working with Zig 0.16.0, focused on changes to the language and standard library. It gathers breaking changes, migration examples, and guidance for consulting local documentation.
Key features
- 01Targets Zig 0.16.0
- 02Summarizes language-level changes
- 03Explains std.Io and standard library API updates
- 04Shows migrations from old APIs to new ones
- 05Guides local language and standard library documentation lookup
Use cases
- 01Migrate a Zig project to 0.16.0
- 02Check version differences in standard library APIs
- 03Update I/O, networking, or file-operation code
- 04Find guidance on Zig 0.16.0 entry points and environment variables
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ zigcc/ skills --skill zig-0.16After installing, type/zig-0.16in Claude Code to confirm it loaded.
Bundled filesscripts/check-zig-version.sh
Details
From the project READMEZig 0.16.0 Programming Guide
Version Scope: This skill is pinned to Zig 0.16.0 (stable). Release Notes: https://ziglang.org/download/0.16.0/release-notes.html
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_dirfor API verification. - Std Library Docs: run
zig stdto start a local HTTP server.
Always check local docs before web search.
Quick Reference: Top Breaking Changes
| Old (0.15.x) | New (0.16.0) |
|---|---|
@Type(.Int(...)) | @Int(.signed, bits) |
@Type(.Struct(...)) | @Struct(layout, BackingInt, field_names, field_types, field_defaults, field_is_comptime, field_alignments) |
@Type(.Pointer(...)) | @Pointer(size, attrs, Element, sentinel) |
@Type(.Fn(...)) | @Fn(param_types, param_attrs, ReturnType, attrs) |
@Type(.Tuple(...)) | @Tuple(field_types) |
@cImport({...}) | b.addTranslateC(...) + @import("c") (deprecated) |
std.net | std.Io.net |
std.ArrayList.init(allocator) | std.ArrayList.initCapacity(allocator, n) |
std.crypto.random | std.Io.randomSecure(io, buf) |
std.meta.intToEnum | std.enums.fromInt |
std.fmt.FormatOptions | std.fmt.Options |
std.fmt.bufPrintZ | std.fmt.bufPrintSentinel |
std.io.fixedBufferStream | std.Io.Writer.fixed(buf) |
error.RenameAcrossMountPoints | error.CrossDevice |
error.NotSameFileSystem | error.CrossDevice |
error.SharingViolation | error.FileBusy |
error.EnvironmentVariableNotFound | error.EnvironmentVariableMissing |
std.Build.Step.ConfigHeader cmake handling | fixed leading whitespace |
Language Changes
@Type Removed — Use Individual Builtins
@Type is gone. Replace with these builtins:
// Integer type
const MyInt = @Int(.signed, 32);
// Struct type
const MyStruct = @Struct(
.auto, // layout
null, // BackingInt (for packed)
&.{"x", "y"}, // field_names
&.{ i32, i32 }, // field_types
&.{ null, null },// field_defaults
&.{ false, false }, // field_is_comptime
&.{ null, null },// field_alignments
);
// Pointer type
const MyPtr = @Pointer(.one, .{
.alignment = 8,
.address_space = .generic,
.is_const = false,
.is_volatile = false,
}, u8, null);
// Function type
const MyFn = @Fn(&.{i32, i32}, &.{.{}, .{}}, i32, .{});
// Tuple type
const MyTuple = @Tuple(&.{ i32, bool });
// Enum literal type
const EnumLitType = @EnumLiteral();
@cImport Deprecated — Use Build System Translation
@cImport is deprecated. Use addTranslateC in build.zig:
// build.zig
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/c.h"),
.target = target,
.optimize = optimize,
});
translate_c.linkSystemLibrary("glfw", .{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.optimize = optimize,
.target = target,
.imports = &.{
.{ .name = "c", .module = translate_c.createModule() },
},
}),
});
Then in Zig: const c = @import("c");
Note: The C translator is now backed by arocc instead of libclang.
Switch Improvements
packed structandpacked unionmay now be used as switch prong items (compared by backing integer).decl literalsand result-type-dependent expressions (e.g.@enumFromInt) may now be used as switch prong items.- Union tag captures are now allowed for all prongs, not just inline ones.
- Switch prong captures may no longer all be discarded.
.awakeatomic order removed; use.monotonic/.seq_cst.
Other Language-Level Gotchas
- Small integer types coerce to floats — e.g.
u8→f32is now allowed. - Runtime vector indexes forbidden — vector indexing must be comptime-known.
- Vectors and arrays no longer support in-memory coercion — must explicitly cast element-wise.
- Trivial local addresses cannot be returned from functions — e.g.
return &local;is rejected more consistently. - Packed unions must not have unused bits — all bit patterns must be valid.
- Pointers forbidden in packed structs/unions.
- Explicitly-aligned pointer types are now distinct from naturally-aligned ones in type system.
- Simplified dependency loop rules — most code still works, but self-referential alignment queries now error.
Standard Library: std.Io
Networking
std.netis gone. Usestd.Io.net(types:IpAddress,Stream,Server,UnixAddress).- High-level helpers like
tcpConnectToHost,Address.parseIp,Stream.connectremoved. UseIovtables:io.vtable.netConnectIp,netRead,netWrite,netListenIp,netAccept. - Raw fd operations: use
std.os.linux.socket,accept4,connect,bind,listen,write,read,close. posix.socket,posix.bind,posix.accept,posix.shutdown,posix.writev,posix.listen,posix.pipe2,posix.getrandom,std.net.has_unix_socketsremoved.- Epoll:
std.os.linux.epoll_create1/epoll_ctl/epoll_waitdirectly. - New:
std.Io.net.Socket.createPairfor 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 callinterface()as a function. std.time.sleepremoved; usestd.Io.sleep(io, Duration, Clock).std.Io.GenericWriter,std.Io.AnyWriter,std.Io.null_writer,std.Io.CountingReaderremoved.
Time / Random / Env / Exit
std.time.milliTimestampremoved. Usestd.time.Timerorstd.Io.Clock.now(clock, io)and compareTimestamp.nanoseconds.- Random secure bytes:
std.Io.randomSecure(io, buf); nostd.crypto.randomorstd.posix.getrandomconvenience. std.process.getEnvVarOwnedremoved; usestd.c.getenvand copy.std.posix.exitremoved; usestd.process.exit.
TLS Client Options
std.crypto.tls.Client.Optionsrequiresentropy: *const [entropy_len]u8andrealtime_now_seconds: i64. Fill entropy withstd.Io.randomSecure; compute seconds withIo.Clock.now(.real, io)/ns_per_s.
MemoryPool API Changes
std.heap.MemoryPool(T).initCapacity(allocator, n)returns the pool.create/destroynow require allocator. No bareinit()or zero-argdeinit().
Format Options
std.fmt.OptionsreplacesFormatOptions.std.fmt.formathelper removed; callwriter.printdirectly (writers live instd.Io.Writer).std.fmt.Formatterrenamed toAlt.std.fmt.bufPrintZrenamed tostd.fmt.bufPrintSentinel.
Randomness / Crypto
std.crypto.randomremoved. Use anstd.Ioinstance:const io = std.Io.Threaded.global_single_threaded.ioBasic(); io.random(&buf);.Ed25519.KeyPair.generatenow requires anio: std.Ioargument.
Enum Conversion
std.meta.intToEnumremoved. Usestd.enums.fromInt(EnumType, value)(returns?EnumType).meta.declListremoved.
Fixed-Buffer Writers in Tests
std.io.fixedBufferStreamremoved. For in-memory writes usevar w = std.Io.Writer.fixed(buf);and read bytes withstd.Io.Writer.buffered(&w).std.ArrayListno longer has.init(allocator)shorthand; use.initCapacity(allocator, n).
Collections
SegmentedListremoved.BitSet,EnumSet: replaceinitEmpty,initFullwith decl literals (e.g..{},.{ .a = true }).- New:
Io.Dir.renamePreserve— rename without replacing destination. std.Io.Dir.renamereturnserror.DirNotEmptyrather thanerror.PathAlreadyExists.
Error Set Renames
// Old -> New
error.RenameAcrossMountPoints -> error.CrossDevice
error.NotSameFileSystem -> error.CrossDevice
error.SharingViolation -> error.FileBusy
error.EnvironmentVariableNotFound -> error.EnvironmentVariableMissing
Math
math.signnow returns the smallest integer type that fits possible values.
Threading
Thread.Mutex.Recursiveremoved.
Compression
lzma,lzma2,xzupdated toIo.Reader/Io.Writerinterfaces.
Dynamic Libraries
DynLibremoved Windows support. UseLoadLibraryExWandGetProcAddressdirectly.
New Execution Model: Juicy Main
Zig 0.16.0 introduces std.process.Init as the preferred main parameter. This is the biggest entrypoint change in this release.
main signatures
// 1. Bare (no args/env available)
pub fn main() !void { }
// 2. Minimal (raw argv + environ only)
pub fn main(init: std.process.Init.Minimal) !void { }
// 3. Juicy Main (recommended)
pub fn main(init: std.process.Init) !void { }
std.process.Init contents
pub const Init = struct {
minimal: Minimal,
arena: *std.heap.ArenaAllocator, // process-lifetime arena, threadsafe
gpa: Allocator, // general-purpose allocator
io: Io, // default Io implementation
environ_map: *Environ.Map, // env vars (not threadsafe)
preopens: Preopens, // WASI-style preopened files
pub const Minimal = struct {
environ: Environ,
args: Args,
};
};
Example
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;
try std.Io.File.stdout().writeStreamingAll(io, "Hello, world!\n");
const args = try init.minimal.args.toSlice(init.arena.allocator());
for (args, 0..) |arg, i| {
std.log.info("arg[{d}] = {s}", .{ i, arg });
}
std.log.info("{d} env vars", .{init.environ_map.count()});
}
Rule of thumb: if your program needs I/O, allocations, or environment variables, use std.process.Init.
Environment Variables & Process Args Are Non-Global
std.os.environ and std.process.args as global state are gone. They are now only available through main's init parameter.
Environment variables
// Juicy Main
for (init.environ_map.keys(), init.environ_map.values()) |key, value| {
std.log.info("env: {s}={s}", .{ key, value });
}
// Minimal main
std.log.info("HOME: {?s}", .{init.environ.getPosix("HOME")});
std.log.info("EDITOR: {s}", .{ try init.environ.getAlloc(arena, "EDITOR") });
const map = try init.environ.createMap(arena);
CLI arguments
// Iterate lazily
var args = init.args.iterate();
while (args.next()) |arg| {
std.log.info("arg: {s}", .{arg});
}
// To slice (needs allocator)
const args = try init.args.toSlice(arena);
Any function that needs environment variables should accept *const process.Environ.Map or process.Environ as a parameter.
Thread.Pool Removed → Use std.Io
std.Thread.Pool is removed. Replace with std.Io.Group, std.Io.async, or std.Io.concurrent.
Migration example
// OLD (0.15)
fn doAllTheWork(pool: *std.Thread.Pool) void {
var wg: std.Thread.WaitGroup = .{};
pool.spawnWg(wg, doSomeWork, .{ pool, &wg, first_work_item });
wg.wait();
}
// NEW (0.16)
fn doAllTheWork(io: std.Io) void {
var g: std.Io.Group = .init;
errdefer g.cancel(io);
g.async(io, doSomeWork, .{ io, &g, first_work_item });
g.wait(io);
}
fn doSomeWork(io: std.Io, g: *std.Io.Group, foo: Foo) void {
foo.doTheThing();
for (foo.new_work_items) |new| {
g.async(io, doSomeWork, .{ io, g, new });
}
}
Important: when migrating from Thread.Pool to Io, convert Thread.Mutex, Thread.Condition, Thread.ResetEvent to Io.Mutex, Io.Condition, Io.Event.
File I/O & fs API Changes
readFileAlloc / readToEndAlloc
// OLD
const contents = try std.fs.cwd().readFileAlloc(allocator, file_name, 1234);
// NEW
const contents = try std.Io.Dir.cwd().readFileAlloc(io, file_name, allocator, .limited(1234));
// Note: FileTooBig -> StreamTooLong when limit reached.
// OLD
const contents = try file.readToEndAlloc(allocator, 1234);
// NEW
var file_reader = file.reader(&.{});
const contents = try file_reader.interface.allocRemaining(allocator, .limited(1234));
setTimestamps
// OLD
try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime);
// NEW
try atomic_file.file_writer.file.setTimestamps(io, .{
.access_timestamp = .init(src_stat.atime),
.modify_timestamp = .init(src_stat.mtime),
});
Io.File.Stat.atime is now ?Timestamp (can be null on e.g. ZFS).
Directory walking
std.Io.Dir.walkstill exists.- New:
std.Io.Dir.walkSelectively— opt-in recursion per directory, avoiding redundant open/close syscalls. - Both
WalkerandSelectiveWalkergainedleave()anddepth().
fs.path.relative is now pure
// OLD
const relative = try std.fs.path.relative(gpa, from, to);
// NEW
const cwd_path = try std.process.currentPathAlloc(io, gpa);
const relative = try std.fs.path.relative(gpa, cwd_path, environ_map, from, to);
fs.path Windows changes
windowsParsePath/diskDesignator/diskDesignatorWindows→parsePath,parsePathWindows,parsePathPosix- Added
getWin32PathType componentIterator/ComponentIterator.initcan 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_TMPFILEwhen 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.
Memory Locking / Protection moved to std.process
// OLD
try std.posix.mlock();
try std.posix.mlock2(slice, std.posix.MLOCK_ONFAULT);
try std.posix.mlockall(slice, std.posix.MCL_CURRENT | std.posix.MCL_FUTURE);
// NEW
try std.process.lockMemory(slice, .{});
try std.process.lockMemory(slice, .{ .on_fault = true });
try std.process.lockMemoryAll(.{ .current = true, .future = true });
mmap/mprotect flags are now type-safe structs instead of bitwise OR:
// OLD
std.posix.PROT.READ | std.posix.PROT.WRITE
// NEW
.{ .READ = true, .WRITE = true }
MemoryPool
std.heap.MemoryPool(T).initCapacity(allocator, n)returns the pool.create/destroynow require allocator parameter.- New unmanaged variants:
MemoryPoolUnmanaged,MemoryPoolAlignedUnmanaged,MemoryPoolExtraUnmanaged.
Io.Writer.Allocating alignment field
alignment: std.mem.Alignment, // new field
Container & Collection Changes
Migration to "Unmanaged"
Managed variants with embedded Allocator fields are being phased out.
ArrayHashMap,AutoArrayHashMap,StringArrayHashMapremoved.AutoArrayHashMapUnmanaged→array_hash_map.AutoStringArrayHashMapUnmanaged→array_hash_map.StringArrayHashMapUnmanaged→array_hash_map.Custom
PriorityQueue
var queue = std.PriorityQueue(u32, void, lessThan).empty;
init→initContextadd→pushaddSlice→pushSliceremove/removeOrNull→popremoveIndex→popIndex
PriorityDequeue
var dq = std.PriorityDequeue(u32, void, lessThan).empty;
init→.emptyadd→pushremoveMinOrNull/removeMin→popMinremoveMaxOrNull/removeMax→popMaxremoveIndex→popIndex
BitSet / EnumSet
Use decl literals instead of initEmpty / initFull:
var set = std.EnumSet(MyEnum).empty;
var full = std.EnumSet(MyEnum).full;
SegmentedList
std.SegmentedListremoved.
Removed APIs (Quick List)
| Removed API | Replacement |
|---|---|
std.Thread.Pool | std.Io.Group, Io.async, Io.concurrent |
std.heap.ThreadSafe | std.heap.ArenaAllocator (now thread-safe) |
std.io.fixedBufferStream | std.Io.Reader.fixed(data) / std.Io.Writer.fixed(buf) |
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.null_writer | Use std.Io.Writer directly |
std.Io.CountingReader | No direct replacement; track bytes manually |
std.SegmentedList | None |
std.meta.declList | None |
std.fs.getAppDataDir | None |
std.posix.getCwd* | std.process.currentPath* |
std.posix.mlock* | std.process.lockMemory* |
std.posix.PROT_* bitwise flags | Struct literal flags |
std.ArrayHashMap | array_hash_map.Auto / .String / .Custom |
Common Idioms in Zig 0.16.0
ArrayList
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
var list = try std.ArrayList(u8).initCapacity(gpa, 16);
defer list.deinit(gpa);
try list.append(gpa, 'a');
try list.appendSlice(gpa, "hello");
try list.ensureTotalCapacity(gpa, 100);
const owned = try list.toOwnedSlice(gpa);
defer gpa.free(owned);
}
HashMap (Default / Unmanaged Style)
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});
File I/O
// Read entire file
const contents = try std.Io.Dir.cwd().readFileAlloc(io, "input.txt", gpa, .limited(1024 * 1024));
defer gpa.free(contents);
// Write file atomically
var atomic = try std.Io.File.Atomic.init(io, gpa, "output.txt");
try atomic.file_writer.interface.print("data: {s}\n", .{"hello"});
try atomic.commit(io);
JSON
const MyStruct = struct {
name: []const u8,
value: u32,
};
// Parsing
const json_str =
\\{"name": "test", "value": 42}
;
const parsed = try std.json.parseFromSlice(MyStruct, gpa, json_str, .{});
defer parsed.deinit();
// Serialization to string (via Io.Writer.Allocating)
var out: std.Io.Writer.Allocating = .init(gpa);
defer out.deinit();
var stringify: std.json.Stringify = .{
.writer = &out.writer,
.options = .{},
};
try stringify.write(parsed.value);
const json_output = out.written();
Type Reflection (Comptime)
const info = @typeInfo(T);
if (info == .pointer) { ... }
if (info == .slice) { ... }
if (info == .@"struct") { ... }
if (info == .@"enum") { ... }
if (info == .@"union") { ... }
if (info == .optional) { ... }
Testing
test "basic arithmetic" {
const std = @import("std");
try std.testing.expectEqual(4, 2 + 2);
try std.testing.expectError(error.OutOfMemory, mightFail());
}
test "no leaks" {
const std = @import("std");
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
const allocator = gpa.allocator();
const ptr = try allocator.create(u32);
defer allocator.destroy(ptr);
}
Leb128 (Binary Format)
// OLD: std.leb.readUleb128(reader)
// NEW:
const value = try reader.takeLeb128(u64);
Common Error Messages and Fixes (0.16)
| Error | Cause | Fix |
|---|---|---|
no field named 'getStdOut' | Using old std.io.getStdOut() | Use std.Io.File.stdout() |
expected 2 argument(s), found 1 | ArrayList method missing allocator | Add allocator as first argument |
no member named 'Pool' in 'Thread' | std.Thread.Pool removed | Use std.Io.Group + Io.async |
expected type '*const process.Environ.Map' | Function needs env map param | Pass it from main(init) |
no member named 'fixedBufferStream' | Old stream API removed | Use std.Io.Reader.fixed(...) or std.Io.Writer.fixed(...) |
no field named 'getCwd' in 'process' | Renamed | Use std.process.currentPath* |
fingerprint field missing | Old build.zig.zon | Add .fingerprint = 0x... and change .name = "x" to .name = .x |
type depends on itself for alignment | Self-referential alignment query | Remove @alignOf(@This()) or restructure |
Build System Changes
Fingerprint Required in build.zig.zon
build.zig.zon now requires a fingerprint field, and name must be an enum literal, not a string:
.{
.name = .myproject, // enum literal, not "myproject"
.fingerprint = 0x123456789abcdef0,
.version = "0.1.0",
.dependencies = .{},
}
zig build will fail if these are missing.
Local Package Overrides: --fork
New CLI flag for temporary local overrides:
zig build --fork=/path/to/local/fork
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.ConfigHeadernow handles leading whitespace for cmake-style config headers correctly.
Compiler / Toolchain
C Translation
@cImportstill exists but is deprecated and now backed by arocc instead of libclang.- For new code, always use
addTranslateCinbuild.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-openbsdnow tested in CI.aarch64-maccatalyst,x86_64-maccatalystcross-compilation added.- Initial
loongarch32-linuxsupport (no libc yet). - 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.createPairor rawsocketpair(AF.UNIX, SOCK.STREAM|CLOEXEC, 0, &fds)to avoid relying on Io vtable network (Threaded nets returnNetworkDownif not wired). - Error sets tightened in many std.Io functions — remove unreachable branches accordingly.
Porting Strategy (0.15 → 0.16)
- Replace
@Typecalls with the specific new builtin functions. - Replace
@cImportwithaddTranslateCinbuild.zig. - Add
fingerprintand fixnameinbuild.zig.zon. - Thread
std.Iothrough your app — any function doing I/O, sleep, random, or time needs anioparameter. - Update
std.netusages tostd.Io.netor raw syscalls. - Update
ArrayListcalls to pass allocator explicitly and useinitCapacity. - Fix error set names (CrossDevice, FileBusy, EnvironmentVariableMissing, DirNotEmpty).
- Run
zig build test --test-timeout 500msto catch hanging tests early.
Use this skill when writing new Zig 0.16.0 code or upgrading from 0.15.x.