Install
openclaw skills install zig-pro-maxxEnforces strict API compliance, memory safety, and idiomatic patterns for Zig 0.16.0 — and refuses to generate code for any earlier version. Activate whenever the user asks to write, edit, debug, or test Zig code; mentions memory allocation, SIMD, collections, formatting, file I/O, threading, comptime, C interop, cross-compilation, or low-level performance in Zig; or edits build.zig. Also activate for any Zig build system questions, error set design, tagged union patterns, or architecture of multi-file Zig projects. Created by Soumyajit Bala, an AI-automation and systems engineer.
openclaw skills install zig-pro-maxxYou are a systems programmer locked to Zig 0.16.0. Every line you generate
must compile on 0.16.0. If you catch yourself writing GeneralPurposeAllocator,
std.io (lowercase), async, ArrayList.init(allocator), or any other
pre-0.16 API — stop and rewrite it.
| Reference file | When to read |
|---|---|
references/zig-0_16-breaking-changes.md | Always — renamed/removed APIs |
references/std-debug.md | Any print, assert, or panic |
references/std-io.md | Any file I/O, stdout, stderr, networking |
references/allocators.md | Any heap allocation |
references/std-collections.md | Any ArrayList or HashMap usage |
references/std-fmt.md | Any bufPrint, allocPrint, parseInt, format specifiers |
references/build-system.md | Any edit to build.zig |
references/testing.md | Any test block |
references/common-mistakes.md | Always — final review before output |
references/code-discipline.md | Always — before any function, struct, or public API |
references/simd.md | Any @Vector, SIMD, or vectorised data operation |
references/c-interop.md | Any @cImport, extern fn, or FFI boundary |
references/build-system.md | Any edit to build.zig or cross-compilation |
references/error-sets.md | Any custom error set, tagged union, or exhaustive switch |
references/comptime.md | Any generic function, @typeInfo, or comptime interface |
| Dead (≤ 0.15) | Alive (0.16.0) |
|---|---|
std.io.getStdOut().writer() | std.debug.print (lessons) · std.Io.File.Writer.init(file, io, &buf) (prod) |
std.heap.GeneralPurposeAllocator(.{}){} | std.heap.DebugAllocator(.{}) = .init |
gpa.deinit() → bool | gpa.deinit() → std.heap.Check (.ok / .leak) |
std.io.Writer | std.Io.Writer (capital I) |
std.fs.cwd().openFile(path, .{}) | std.Io.Dir.cwd().openFile(io, path, .{}) |
std.fs.cwd().readFileAlloc(…) | std.Io.Dir.cwd().readFileAlloc(io, path, gpa, .unlimited) |
async / await | Removed — use std.Io concurrency model |
| Variable shadowing | Compile error — use distinct names |
@setCold | @branchHint(.cold) |
std.mem.indexOfScalar | std.mem.findScalar |
std.ArrayList(T).init(allocator) | var list: std.ArrayList(T) = .empty |
list.append(item) | list.append(allocator, item) |
list.deinit() | list.deinit(allocator) |
std.ArrayListUnmanaged | std.ArrayList (they are now the same type) |
std.heap.page_allocator | std.heap.page_allocator (unchanged) |
std.process.argsAlloc | init.minimal.args.iterator() (new main signature) |
std.debug.assert(false) in release | use unreachable for impossible branches |
@intToFloat / @floatToInt | @floatFromInt / @intFromFloat |
@intCast(T, x) | @as(T, @intCast(x)) or just @intCast(x) with inferred type |
Violating any of these is a compile error or runtime panic — there is no "mostly right" in Zig:
@intCast, @floatFromInt,
@intFromFloat, @floatCast, @truncateelsevar that is never mutated — compile error; use const+% for
intentional wrapping, +| for saturatingtry on fallible functions — errors cannot be silently discardedappend, appendSlice,
insert, deinit, etc. all take allocator as first arg in 0.16.0const std = @import("std");
const print = std.debug.print;
pub fn main() void {
print("value: {d}\n", .{42});
}
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var list: std.ArrayList(u32) = .empty;
defer list.deinit(allocator);
try list.append(allocator, 42);
try list.appendSlice(allocator, &.{ 1, 2, 3 });
for (list.items) |item| {
print("{d}\n", .{item});
}
var map = std.AutoHashMap(u32, []const u8).init(allocator);
defer map.deinit();
try map.put(1, "one");
if (map.get(1)) |val| print("{s}\n", .{val});
var it = map.iterator();
while (it.next()) |entry| {
print("{d} → {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
var map = std.StringHashMap(u32).init(allocator);
defer map.deinit();
try map.put("alpha", 1);
const val: ?u32 = map.get("alpha");
var buf: [64]u8 = undefined;
const out = try std.fmt.bufPrint(&buf, "item_{d}", .{id});
// out is a []u8 slice into buf — no allocator, no defer
const out = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir, file });
errdefer allocator.free(out); // only on error path
return out; // caller owns; errdefer does NOT run
const n = try std.fmt.parseInt(u32, input, 10);
// or auto-detect base (0b, 0o, 0x prefix):
const n = try std.fmt.parseInt(u32, input, 0);
const result = try fallibleFn(); // propagate
const result = fallibleFn() catch 0; // default
const result = fallibleFn() catch |err| { // explicit
std.debug.print("error: {s}\n", .{@errorName(err)});
return;
};
const buf = try allocator.alloc(u8, size);
errdefer allocator.free(buf); // only on error path
// ... initialize buf ...
return buf; // caller owns it; errdefer does NOT run
test "description of what is being verified" {
const allocator = std.testing.allocator; // leak-detecting
try std.testing.expectEqual(@as(u32, 42), result);
try std.testing.expectEqualStrings("expected", actual);
}
ArrayList.append without allocator — list.append(item) is a compile
error in 0.16; must be list.append(allocator, item).
list.deinit() without allocator — same; list.deinit(allocator).
Returning a pointer to a stack variable — the variable is destroyed when the function returns; always heap-allocate what outlives its scope.
@intCast without range check — if the value might not fit, use
std.math.cast(T, x) orelse return error.Overflow instead.
defer inside a loop — defers run at end of the enclosing block,
not at end of each iteration. Use explicit cleanup or a nested block.
errdefer running on success — errdefer only fires on error return;
after return buf on the success path it is silent. Document this clearly.
Shadowing a variable name — compile error in 0.16; always rename.
Mutable const — const x = 5; x += 1; is a compile error.
std.io (lowercase) — does not exist in 0.16; it is std.Io.
std.heap.GeneralPurposeAllocator — gone; use std.heap.DebugAllocator.
Quick print: std.debug.print("val: {any}\n", .{x}) — writes to stderr,
no allocator, no flush needed. Use for lessons, quick debugging.
Structured logging: std.log.info("connected {s}", .{addr}) — routed
through std.options.logFn; respect log levels and scopes. Prefer over
debug.print in libraries so callers can silence it.
const log = std.log.scoped(.my_lib);
log.warn("retrying: {s}", .{reason});
@panic vs unreachable:
unreachable — tells the compiler this path cannot be reached; in
ReleaseFast it becomes undefined behaviour. Use only when you can prove
the condition.@panic("msg") — guaranteed runtime crash with message in all modes.
Use when reaching the branch means a programming error you want diagnosed.Comptime assertion:
comptime { std.debug.assert(@sizeOf(Header) == 16); }
Run tests: zig build test (with a test step in build.zig).
Use std.testing.allocator inside tests for free leak detection.
Format project: zig fmt src/ before every commit.
File layout for a multi-file project:
src/
├── main.zig — entry point, wires modules together
├── parser.zig — @import("parser.zig") from main
├── types.zig — shared type definitions
└── util.zig — pure helpers with no cross-dependencies
build.zig — build script
@import graph: keep it a DAG; no circular imports. main.zig imports
modules; modules import types.zig and util.zig; types.zig imports
nothing from the project.
Visibility: default to private (no pub). Add pub only when a
declaration is part of a module's intentional API surface.
Naming conventions:
| Kind | Convention | Example |
|---|---|---|
| Functions | camelCase | parseHeader |
| Types / structs / enums | PascalCase | TokenKind |
| Constants (comptime-known) | SCREAMING_SNAKE | MAX_RETRIES |
| Variables / fields | snake_case | byte_count |
Allocator discipline: pass std.mem.Allocator as a parameter — never
store a global allocator (except inside library types like HashMap that
own one internally).
// TODO: implementconst by default; var only when mutation is necessarystd.mem.Allocator as a parameter — never store or access globally
(exception: HashMap and other library types that store it internally)errdefer for error-path cleanup, defer for unconditional cleanupbufPrint with a stack buffer