| name | zig-pro-maxx |
| description | Enforces 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.
|
| metadata | {"trigger":"Writing zig code files, editing drafts for zig, reviewing content for Zig.","author":"Soumyajit Bala (https://github.com/debuggerdragon311)","version":"v0.1.9-beta3"} |
ZIG-PRO-MAXX — 0.16.0 ONLY, NO EXCEPTIONS
You 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.
Load these references before you write a single line
| Reference file | When to read |
|---|
references/code-discipline.md | Always follow and must — before any function, struct, or public API or any code or any comment |
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/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 |
Check these examples before you write a single line (Don't mention it on users end, its for grounding with actual code for more better understanding)
| Example file | When to read |
|---|
examples/hello_from_cli_args.md | Any args-related code |
examples/sample_0_16.md | Any time you want an overview of basics |
examples/fibo.md | Any time you want to know to a teminal i/o works for fibo |
examples/factorial.md | Any time you want to know to a teminal i/o works for factorial |
examples/swap_strings_of_2.md | Any time you want to know to a teminal i/o works for swap of 2 strings |
examples/hello.md | Any time you want to know to a teminal i/o works for a simple greeting program |
examples/adder.md | Any time you want to know to a teminal i/o works for a simple program to add 2 numbers |
examples/isprime.md | Any time you want to know to a teminal i/o works for a simple program to check prime numbers |
To check actual code files written in zig in .zig files (for more references) --
examples/actual_code_files/**
Same files just in .zig extension
The API swap table — memorise this
| 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 |
| std.mem.trimLeft | std.mem.trimStart |
| std.mem.trimRight | std.mem.trimEnd |
Non-negotiable compiler rules
Violating any of these is a compile error or runtime panic — there is no
"mostly right" in Zig:
- No implicit numeric coercion — use
@intCast, @floatFromInt,
@intFromFloat, @floatCast, @truncate
- No local variable shadowing — compile error; always use distinct names
- Exhaustive switch on enums — every variant or an explicit
else
var that is never mutated — compile error; use const
- Integer overflow in Debug/ReleaseSafe — runtime panic; use
+% for
intentional wrapping, +| for saturating
try on fallible functions — errors cannot be silently discarded
- ArrayList mutations require allocator —
append, appendSlice,
insert, deinit, etc. all take allocator as first arg in 0.16.0
Canonical patterns — copy these exactly
Debug output (all lesson files)
const std = @import("std");
const print = std.debug.print;
pub fn main() void {
print("value: {d}\n", .{42});
}
Heap allocation
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
ArrayList (0.16.0 — unmanaged, allocator passed per call)
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});
}
AutoHashMap
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.* });
}
StringHashMap
var map = std.StringHashMap(u32).init(allocator);
defer map.deinit();
try map.put("alpha", 1);
const val: ?u32 = map.get("alpha");
Format into stack buffer (prefer over allocPrint in hot paths)
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
Format into heap (when size is unknown)
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
Parse an integer from a string
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);
Error handling
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;
};
Ownership and cleanup
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
Tests
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);
}
Inlined critical mistakes (check every output against these)
-
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.
Debugging & tooling
-
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.
Architecture & naming conventions
-
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).
Code quality rules
- Every file must compile — no pseudocode, no
// TODO: implement
const by default; var only when mutation is necessary
- Pass
std.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 cleanup
- Format specifiers must match argument types exactly — enforced at compile time
- Comments explain why, not what
- No allocations inside hot loops — use
bufPrint with a stack buffer
- ArrayList: always pass the allocator; StringHashMap: ensure key lifetime
exceeds the map entry's lifetime
- Try to follow only ascii cheracters as much as possible