Zig 0.17
Facts here were checked against the 0.17.0 release notes, language reference,
standard library source, and a real 0.16 to 0.17 port. Trust them before
re-deriving 0.17 from lib/std or the web; read the std source only for an API
this skill does not cover.
Project rules (for example an AGENTS.md) win over this skill for local helpers
and conventions. General Zig quality belongs to zig-best-practices.
Check the compiler first
Read .minimum_zig_version in build.zig.zon and run zig version. Do not
apply these rules to a project that still targets 0.16.
When the project targets 0.17 but zig is 0.16, do not build with it. Zig 0.16
ignores minimum_zig_version and reports correct 0.17 code as broken, for
example no field or member function named 'print' in 'mem.Allocator' or
no field named 'debug' in enum 'builtin.OptimizeMode'. Never "fix" such code
back to 0.16 names. Find a 0.17 compiler with which -a zig and in versioned
install directories, then run it by absolute path or with a per-command
PATH=<dir>:$PATH. Do not upgrade, relink, or replace the shared zig, because
other checkouts may still need 0.16. If there is no 0.17 compiler, install one
next to 0.16 (toolchain) or ask, and say which checks
did not run on 0.17.
Changes that compile but change behavior
A clean build proves none of these. Check every one during a migration.
@hasDeclonly seespubdeclarations, even from the same file. A duck-typed probe of a non-pubdeclaration now returnsfalseand silently disables the feature. Make the probed declarationpub.std.mem.containsAtLeastScalar(T, list, element, minimum)swapped its last two parameters (0.16 wasminimum, element). Oldu8calls still compile and return wrong answers. Rewrite every call site.@bitCastwas redefined. Casts involving arrays or vectors now concatenate logical bit representations (endian-agnostic). Casts from or toextern struct/extern unionare rejected: pun through@as(*align(1) const T, @ptrCast(&value))instead. A cast to an enum safety-checks the tag.@tagName(builtin.optimize)yieldsdebug,safe,fast,small. If a user sees the label, map it explicitly instead of printing the tag.std.mem.eql/findDiffon floats no longer treat a slice as equal to itself (NaN is not equal to NaN).std.array_list.Managed(which backsstd.json.Array) gained a requiredpointer_stabilityfield. Never build one with a field literal; use.fromOwnedSlice(gpa, items)or.init(gpa).
Removed: these no longer compile
| 0.16 | 0.17 |
|---|---|
[1]T{v} ** n, "ab" ** n | const a: [n]T = @splat(v);; for repeated strings use a comptime helper (language) |
errdefer |err| ... | Split the function; handle the error in the caller |
void{} | {} |
i0 | u0 |
@cImport | The translate-c package (build system) |
opt == .ReleaseSafe and other old tags in ==/!= | .debug, .safe, .fast, .small |
std.heap.StackFallbackAllocator, stackFallback | std.heap.BufferFirstAllocator.init(buffer, fallback) |
@typeInfo(T).@"struct".fields | .field_names, .field_types, .field_attrs, .decl_names |
b.build_root, if (b.args) |a| run.addArgs(a) | b.root (a Cache.Path), run.addPassthruArgs() |
The full list, including std.ascii, bit sets, packed ints, Uri.getHost, and
std.zon.parse, is in std.
Deprecated: still compiles, do not write it
| Deprecated | Use |
|---|---|
std.fmt.allocPrint(gpa, f, a) | gpa.print(f, a) (same error set) |
std.fmt.bufPrint(buf, f, a), bufPrintSentinel, BufPrintError | std.mem.print, std.mem.printSentinel, std.mem.PrintError |
std.mem.indexOf*, lastIndexOf* | std.mem.find, findScalar, findAny, findPos, findLast, findScalarLast, findLastAny |
std.mem.copyForwards(T, d, s) / copyBackwards | @memmove(d[0..s.len], s); @memmove requires equal lengths |
anything in std.fs | std.Io.Dir.path, std.Io.Dir.max_path_bytes, std.Io.Dir.max_name_bytes |
path.resolve, path.relative | std.Io.Dir.path.resolveAlloc, relativeAlloc |
@import("builtin").os, .cpu, .abi, .object_format | builtin.target.os, .target.cpu, .target.abi, .target.ofmt |
builtin.mode, std.debug.runtime_safety | builtin.optimize, builtin.optimize.runtimeSafety() |
std.builtin, std.lang.OptimizeMode | std.lang, std.lang.Optimize |
@intFromEnum, @enumFromInt | @backingInt, @fromBackingInt (zig fmt upgrades them) |
std.ArrayListUnmanaged, *ArrayHashMapUnmanaged, *BitSet* | std.ArrayList, std.array_hash_map.String/Auto/Custom, std.bit_set.Integer/Array/Static/Dynamic |
list.getLast(), getLastOrNull() | list.last().?, list.last() |
std.heap.DebugAllocator | std.heap.SafeAllocator |
std.unicode.utf8Decode | Validate with utf8ByteSequenceLength, then utf8Decode2/3/4 |
std.meta.fieldNames, fieldInfo, fieldTypes | @typeInfo |
Run addArtifactArg, addFileArg, addPrefixed*Arg, ... | addArtifactArg2(x, .{ .prefix = "-p" }), addFileArg2, ... |
b.addTranslateC | The translate-c package |
Every deprecated name in this table forwards to its replacement, so the rename
cannot change behavior; the only exceptions are the copyForwards and
utf8Decode rows. std.Io.Dir.path is the same module as std.fs.path, so
join, basename, dirname, and the rest carry over unchanged. Rarer
deprecations (readAlloc, sendMany, Target.isAARCH64, tar, default .{}
initialization of hash maps) are in std.
New in 0.17 worth using
@divCeil(a, b)instead ofstd.math.divCeil(T, a, b) catch unreachable.- A slice with a comptime-known length coerces to an array pointer and
dereferences to an array:
const a: [3]u16 = slice.*;. std.heap.SafeAllocatorfor leak and misuse detection in tests and debug tools. It never reuses memory, so it is not a production allocator.list.last()andlist.lastPtr(),std.Io.Semaphore.waitTimeout,SafetyLock.lockShared,std.zon.parse.updateFrom*.- Build system:
findProgramLazy,dependOnFileContents,b.pathList,addOptionPathDirectory,dependencyLazy,--print-configuration. zig build -fincremental --watchworks for most x86_64-linux projects.zig fmt --complexityreports token and AST-node counts per file.
Writing new 0.17 code
Use only the right-hand columns above. Before reporting the work done, run the bundled scanner and require zero removed and zero deprecated findings:
python3 <this skill>/scripts/check_deprecated.py <files or directories>
Migrating 0.16 code
-
If the migration touches CI, release targets, or OS minimums, read toolchain first. A single-file migration does not need it.
-
Apply the mechanical renames, then let
zig fmt(0.17) rewrite@intFromEnumand@enumFromInt:python3 <this skill>/scripts/migrate.py --write <files or directories> zig fmt <files or directories>scripts/migrate.pyonly makes renames that forward exactly to their 0.17 declaration (allocPrint,bufPrint,indexOf*,builtin.os,builtin.modecomparisons,ArrayListUnmanaged,std.builtin,std.fs.path, and more). It never edits strings or comments, and it prints aNOTEfor each site that needs judgment. Without--writeit only reports. -
Fix compile errors, but do not stop there. Zig analyzes lazily, so build every artifact the project ships and run the full test suite, not one
zig build. -
Run
check_deprecated.py --review. Replace every removed and deprecated finding that remains (**,errdefercaptures,copyForwards, allocators, and so on), and read every review hotspot against the six behavior changes above. -
Rewrite
copyForwardssites individually:@memmove(d, s)is only correct when the lengths already match, so trim the destination tos.lenotherwise. -
Add a test for each silent change you fix, so a later merge cannot undo it.
-
Report what remains deprecated on purpose and why, for example a translate-c dependency the project has not approved.
Merging an upgraded main into a 0.16 branch: merge instead of rebasing,
so each conflict is resolved once and no intermediate commit has to build on
0.17. Where a conflict differs only in 0.16 spellings, take main's side; where
the branch changed behavior, keep the change and write it the 0.17 way. Then run
steps 2 to 4 on the whole merged tree. migrate.py leaves code that is already
0.17 unchanged, so it only rewrites what the branch added.
Reviewing Zig code
Run the scanner on the changed files and check each finding. Treat a new
@hasDecl probe, containsAtLeastScalar call, or @bitCast of an array, an
extern struct, or into an enum as a review question, not a style nit.
References
The tables above cover most tasks. Load a reference only when the task needs more than they say, because each one costs context:
- references/language.md: when a change touches casts, builtins, comptime, reflection, or removed syntax and you need before and after code.
- references/std.md: when you need an exact signature, an allocator or container API, ZON parsing, or a rare deprecation.
- references/build-system.md: when editing
build.zig,build.zig.zon, package fetching, translate-c, or editor tooling. - references/toolchain.md: when installing 0.17 next to 0.16, changing CI, release targets, OS minimums, or libc, and for gotchas from a real port.
- references/examples.zig: compile-checked snippets
for the patterns above.
zig test references/examples.zigpasses on 0.17.0.