zig-guide
SKILL.md
Zig Guide
Applies to: Zig 0.13+, Systems Programming, CLIs, Embedded, Game Engines, C/C++ Replacement
Core Principles
- Explicit Over Implicit: No hidden control flow, no hidden allocations, no operator overloading, no implicit conversions
- Allocator Passing: Every function that allocates receives an
std.mem.Allocatoras a parameter; never use a global allocator - Errors Are Values: Use error unions (
!) withtry,catch, anderrdefer; never silently discard errors - Comptime Over Runtime: Move computation to compile time with
comptime; use it for generics, validation, and code generation - Safety With Escape Hatches: Keep runtime safety enabled by default; disable only in measured hot paths with a justifying comment
Guardrails
Code Style
- Run
zig fmtbefore every commit (non-negotiable) camelCasefunctions/variables,PascalCasetypes/structs/enums,SCREAMING_SNAKE_CASEmodule constants- Prefer
snake_casefor file names (my_module.zig) - Discard unused values explicitly:
_ = value;(no_prefix naming) - Prefer
constovervareverywhere; mutability requires justification - All public declarations (
pub fn,pub const) must have a doc comment (///)
Memory and Allocators
- Every function that heap-allocates must accept an
std.mem.Allocatorparameter - Always pair
alloc/freewithdeferorerrdeferat the call site - Use
std.heap.ArenaAllocatorfor batch allocations freed together - Use
std.heap.GeneralPurposeAllocatorin debug builds (detects leaks, double-free) - Never store an allocator in a struct unless the struct owns long-lived memory
- Prefer stack allocation (
var buf: [256]u8 = undefined;) for small, bounded buffers - Slices (
[]T,[]const T) over raw pointers ([*]T) whenever possible
Error Handling
- Return error unions (
!T) for all fallible operations - Use
tryto propagate; usecatchonly where you can handle or log meaningfully - Use
errdeferto clean up resources on error paths - Define domain-specific error sets; avoid
anyerrorexcept at top-level boundaries - Never ignore errors:
_ = fallibleFn();is forbidden outside tests
const ConfigError = error{ FileNotFound, InvalidFormat, MissingField };
fn loadConfig(allocator: std.mem.Allocator, path: []const u8) ConfigError!Config {
const file = std.fs.cwd().openFile(path, .{}) catch return error.FileNotFound;
defer file.close();
// parse and return ...
}
Comptime
- Use
comptimeparameters for generics instead of runtime polymorphism - Use
@typeInfoand@Typefor type introspection at compile time - Use
@compileErrorto produce clear messages for invalid type combinations - Prefer
inline foronly when the loop count is comptime-known and small
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
fn ensureUnsigned(comptime T: type) void {
if (@typeInfo(T) != .int or @typeInfo(T).int.signedness == .signed)
@compileError("expected unsigned integer type");
}
Safety
- Keep runtime safety enabled in debug and test builds
- Validate all external inputs (file I/O, network, FFI boundaries)
- Use
std.math.add/std.math.mulfor checked arithmetic; sentinel slices ([:0]const u8) for C strings - Avoid
@ptrCastand@intFromPtrunless interfacing with C; justify each use
Key Patterns
Optionals
fn findUser(users: []const User, id: u64) ?*const User {
for (users) |*user| {
if (user.id == id) return user;
}
return null;
}
const user = findUser(users, 42) orelse return error.UserNotFound;
if (findUser(users, 42)) |u| {
std.debug.print("Found: {s}\n", .{u.name});
}
Defer and Errdefer
fn createConnection(allocator: std.mem.Allocator, host: []const u8) !*Connection {
const conn = try allocator.create(Connection);
errdefer allocator.destroy(conn); // only runs on error return
conn.* = .{ .socket = try std.net.tcpConnectToHost(allocator, host, 8080), .allocator = allocator };
errdefer conn.socket.close();
try conn.performHandshake();
return conn;
}
Slices vs Arrays
const fixed: [4]u8 = .{ 1, 2, 3, 4 }; // fixed-size array (stack)
const c_str: [:0]const u8 = "hello"; // sentinel-terminated (C compat)
fn sum(values: []const u32) u64 { // slice (preferred for params)
var total: u64 = 0;
for (values) |v| total += v;
return total;
}
Packed and Extern Structs
const StatusRegister = packed struct {
carry: u1, zero: u1, irq_disable: u1, decimal: u1,
brk: u1, _reserved: u1, overflow: u1, negative: u1,
};
const CFileHeader = extern struct { magic: u32, version: u16, flags: u16, size: u64 };
Testing
const testing = @import("std").testing;
test "add returns correct sum" {
try testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "string list detects leaks via testing allocator" {
var list = StringList.init(testing.allocator); // leak detection built-in
defer list.deinit();
try list.add("hello");
try testing.expectEqual(@as(usize, 1), list.items.items.len);
}
test "readFileContents returns error for missing file" {
try testing.expectError(error.FileNotFound, readFileContents(testing.allocator, "/nonexistent"));
}
Testing Standards
- Test names describe behavior:
test "user creation fails with empty name" - Unit tests live in
testblocks inside the same.zigfile - Integration tests go in
tests/directory - Use
std.testing.allocator-- it detects memory leaks automatically - Key assertions:
expectEqual,expectError,expectEqualStrings,expectEqualSlices - Coverage target: >80% for library code, >60% for application code
Tooling
zig build # Build (uses build.zig)
zig build test # Run all tests
zig build run # Build and run
zig fmt src/ # Format all source files
zig test src/main.zig # Test a single file
zig build -Doptimize=ReleaseSafe # Optimized with safety
zig build -Doptimize=ReleaseFast # Maximum performance
See patterns.md for a build.zig template.
C Interop
- Use
@cImport/@cIncludefor C headers (not manual extern declarations) - Use
[*c]Tonly at FFI boundary; convert to[]Tor?*Timmediately - Use
std.mem.spanto convert[*:0]const u8(C string) to Zig slice - Always call
exe.linkLibC()in build.zig when using libc - Validate all values received from C before using in safe Zig code
References
For full implementation examples, see:
- references/patterns.md -- Arena allocator, GPA leak detection, comptime generics, comptime interface validation, C library wrapper (SQLite), sentinel string conversion, tagged union state machines, errdefer chains, build.zig template
External References
Weekly Installs
6
Repository
ar4mirez/samuelGitHub Stars
3
First Seen
Mar 1, 2026
Security Audits
Installed on
cline6
github-copilot6
codex6
kimi-cli6
gemini-cli6
cursor6