This appendix records the kinds of changes that matter when moving older Zig code to Zig 0.16. It is a practical checklist, not a full release history.
This appendix records the kinds of changes that matter when moving older Zig code to Zig 0.16. It is a practical checklist, not a full release history.
H.1 Start With the Compiler
Run the new compiler first.
zig version
zig build
zig test src/main.zigFix errors in this order:
- build script errors
- import and module errors
- standard library API errors
- type and builtin errors
- runtime behavior changes
Do not rewrite the program before reading the first compiler error.
H.2 Build Script Changes
Old build scripts usually fail before source files compile.
Check:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
}Artifacts should receive .target and .optimize.
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});Use b.path(...) for project-relative paths.
.root_source_file = b.path("src/main.zig")H.3 Modules and Imports
Prefer explicit modules.
const lib_mod = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("lib", lib_mod);Then import by name:
const lib = @import("lib");This is cleaner than relying on path-shaped imports throughout the program.
H.4 Package Dependencies
Use build.zig.zon for package metadata and dependencies.
Typical project files:
build.zig
build.zig.zon
src/main.zigA dependency is read in build.zig.
const dep = b.dependency("name", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("name", dep.module("name"));Source files import the module.
const name = @import("name");H.5 Standard Library Movement
The standard library changes between Zig versions. Treat std as source code you can inspect.
Search by name:
grep -R "pub fn openFile" "$(zig env | jq -r .std_dir)"Or open the standard library directory:
zig envCommon areas that may need edits:
std.fs
std.io
std.mem
std.process
std.Build
std.http
std.jsonWhen an API changed, do not guess the replacement from memory. Read the new declaration.
H.6 I/O Code
I/O APIs are common migration points.
Old code often has one of these shapes:
const stdout = std.io.getStdOut().writer();
try stdout.print("hello\n", .{});or file reading code like:
const data = try file.readToEndAlloc(allocator, max_size);Check Zig 0.16 declarations directly if these fail. I/O is an area where names and interfaces may move during standard library work.
H.7 Memory Utilities
Check old memory calls.
std.mem.copy
std.mem.setModern code commonly uses more explicit names or builtins.
std.mem.copyForwards
std.mem.copyBackwards
@memcpy
@memsetUse copyForwards or copyBackwards when overlap direction matters. Use @memcpy when the source and destination must not overlap.
H.8 Integer Casts
Older code may use casts that no longer match the current builtin signatures.
Prefer destination type from context:
const x: u32 = @intCast(n);Use @as when the result type is not clear from context.
const x = @as(u32, @intCast(n));Do the same for floats and enum conversion where needed.
H.9 Pointer Casts
Pointer casts should be explicit about intent.
const p: *T = @ptrCast(raw);If alignment increases, add @alignCast.
const p: *T = @alignCast(@ptrCast(raw));If the conversion changes constness, fix the type instead of forcing the cast.
H.10 Error Handling
Old code may ignore errors.
file.writeAll(data);Zig rejects this.
try file.writeAll(data);Or handle the error locally.
file.writeAll(data) catch |err| {
return err;
};Do not silence errors with catch {} unless losing the error is correct.
H.11 Optional Handling
Check old null-handling code.
const value = maybe.?;A force unwrap is acceptable only when null means a programmer bug.
Safer forms:
const value = maybe orelse return error.MissingValue;if (maybe) |value| {
use(value);
}H.12 comptime Code
Compile-time code may break because of stricter type checking or changed reflection shapes.
Common places:
@typeInfo
@Type
@hasDecl
@hasField
@field
inline forKeep compile-time code small. Print intermediate values with:
@compileLog(x);Remove @compileLog after the migration.
H.13 Tests
Run all tests after each group of edits.
zig build test
zig test src/main.zigUse std.testing.allocator in tests that allocate.
const allocator = std.testing.allocator;Free what you allocate.
const buf = try allocator.alloc(u8, 1024);
defer allocator.free(buf);H.14 C Interop
Check C interop in three places:
@cImport blocks
extern declarations
build.zig linkingBuild script:
exe.linkLibC();
exe.addIncludePath(b.path("include"));
exe.linkSystemLibrary("sqlite3");C declaration:
extern fn puts(s: [*:0]const u8) c_int;When translated headers fail, reduce the header surface. Import the smallest C header that gives the declarations you need.
H.15 Release Modes
Check behavior in all modes that the project supports.
zig build -Doptimize=Debug
zig build -Doptimize=ReleaseSafe
zig build -Doptimize=ReleaseFast
zig build -Doptimize=ReleaseSmallDebug and ReleaseSafe keep more safety checks. ReleaseFast and ReleaseSmall remove many checks.
Do not rely on a safety trap as part of normal program behavior.
H.16 Migration Checklist
Use this order:
1. install Zig 0.16
2. run zig version
3. run zig build
4. fix build.zig
5. fix modules and imports
6. fix standard library API calls
7. fix builtin calls and casts
8. fix error handling
9. run tests
10. run release buildsA good migration leaves the code simpler than before. Delete compatibility wrappers once all code uses the new form.