$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

AI Coding Rules for Zig Teams

Zig's explicit philosophy — no hidden control flow, no hidden allocations — requires AI rules that prevent C-style shortcuts and enforce Zig's safety patterns.

7 min read·March 5, 2026

No hidden allocations, no hidden control flow — Zig demands explicit AI rules

Allocators, error unions, comptime, and Zig's radical transparency

Why Zig Needs Specific AI Coding Rules

Zig's design philosophy is radical transparency: no hidden control flow, no hidden allocations, no hidden behavior. Every allocation is explicit (you pass an allocator), every error is an error union that must be handled, and there are no exceptions, no garbage collector, and no hidden function calls from operator overloading. AI assistants trained on C, C++, Rust, and Go generate code that violates all of these principles.

The most common AI failures in Zig: using C-style malloc instead of Zig allocators, ignoring error unions with catch unreachable, generating C-style string handling instead of Zig slices, missing defer for cleanup, and using runtime logic where comptime would be appropriate.

Zig is still a younger language with a smaller training data footprint than Rust or Go. This means AI assistants have less Zig-specific training and are more likely to apply patterns from similar languages (C, Rust) that don't translate directly.

Rule 1: Explicit Allocators Everywhere

The rule: 'Every function that allocates memory must accept an allocator parameter (std.mem.Allocator). Never use a global or implicit allocator. Pass allocators through the call chain — the caller decides the allocation strategy, not the callee. Use defer to free allocations in the same scope they were created. Use ArenaAllocator for temporary allocations that can be freed in bulk.'

For common patterns: 'Use allocator.alloc for dynamic arrays. Use allocator.create for single objects. Always pair allocations with defer allocator.free or defer allocator.destroy. Use std.ArrayList for growable arrays — pass the allocator at construction.'

This is Zig's most distinctive feature. The allocator is always explicit — the function signature tells you whether it allocates. AI code that hides allocations is fundamentally un-Zig.

💡 Allocator in the Signature

If a function allocates, the allocator is in its parameter list. If it doesn't allocate, there's no allocator parameter. This makes allocation cost visible at the API level — Zig's core design principle.

Rule 2: Error Union Handling

The rule: 'Handle all error unions explicitly. Use try for propagating errors to the caller. Use catch to handle errors at the current level — always provide a meaningful fallback or return an error. Never use catch unreachable unless you can prove the error cannot occur. Use errdefer for cleanup that should only happen on error paths.'

For error sets: 'Define custom error sets for your domain: const UserError = error{ NotFound, InvalidEmail, AlreadyExists }. Return error unions with domain errors: fn getUser(id: u64) UserError!User. Use anyerror only at the top-level boundary — be specific in library code.'

Error unions are Zig's mechanism for safe error handling without exceptions. catch unreachable is the equivalent of unwrap() in Rust — it crashes on error. The AI should almost never generate it.

  • try to propagate errors up the call chain
  • catch with meaningful fallback — never catch unreachable in production
  • errdefer for cleanup on error paths only
  • Custom error sets for domain errors — anyerror only at boundaries
  • Error unions in return types make error handling visible in signatures
⚠️ Never catch unreachable

`catch unreachable` is Zig's equivalent of Rust's unwrap() — it crashes on error. AI generates it as a shortcut. In production, always catch with a meaningful fallback or propagate with try.

Rule 3: Comptime for Metaprogramming

The rule: 'Use comptime for: generic programming, compile-time validation, configuration, and type-level computation. Use @typeInfo for type reflection at compile time. Use inline for and inline while for unrolling known-length loops. Prefer comptime computation over runtime computation when the inputs are known at compile time.'

For generics: 'Use comptime parameters for generic functions: fn max(comptime T: type, a: T, b: T) T. This is Zig's generics mechanism — no templates, no trait bounds, just comptime type parameters. The compiler generates specialized code for each concrete type.'

AI assistants often ignore comptime entirely, generating runtime code for things that could be resolved at compile time. In a language designed for systems programming, compile-time computation is a core tool — not an optimization trick.

Rule 4: Memory Safety Without a Borrow Checker

Zig doesn't have Rust's borrow checker — memory safety relies on discipline, runtime safety checks (in debug mode), and explicit patterns. AI assistants coming from Rust or C generate code that's either too loose (C-style) or too defensive (trying to emulate Rust ownership).

The rule: 'Use slices ([]const u8) instead of raw pointers for data access. Use sentinel-terminated slices ([:0]const u8) for C-interop strings. Use std.debug.assert for invariant checking in debug builds. Use defer/errdefer religiously for resource cleanup — every allocation gets a corresponding defer free. Never return a pointer to stack memory.'

For testing: 'Use std.testing.allocator in tests — it detects memory leaks. Use std.testing.expect for assertions. Run tests with zig test which includes runtime safety checks (bounds checking, use-after-free detection in debug mode).'

ℹ️ Test Allocator Detects Leaks

Use std.testing.allocator in tests — it automatically detects memory leaks. If your test passes but leaks memory, the test fails. This is Zig's built-in memory safety net.

Rule 5: Zig Style and Conventions

The rule: 'Follow Zig style conventions: camelCase for functions and variables, PascalCase for types and comptime values, SCREAMING_CASE for compile-time constants. Use doc comments (///) for public API documentation. Keep functions short — extract named functions for clarity. Use labeled blocks and labeled breaks for complex control flow instead of deeply nested if-else.'

For project structure: 'Use build.zig for the build system — never external build tools (Make, CMake) unless wrapping C dependencies. Organize code in src/ with one concept per file. Use @import for module imports. Use pub for public API — everything is private by default.'

For C interop: 'Use @cImport for C header imports. Use std.c for C standard library functions. Keep C interop at the boundary — wrap C APIs in Zig-idiomatic interfaces. Never expose raw C pointers in your public Zig API.'

Complete Zig Rules Template

Consolidated template for Zig teams.

  • Explicit allocators in every allocating function — caller decides allocation strategy
  • defer/errdefer for all resource cleanup — pair every alloc with a free
  • Handle error unions explicitly — try to propagate, catch with fallback, never catch unreachable
  • comptime for generics, validation, and compile-time computation
  • Slices over raw pointers — sentinel-terminated for C strings
  • std.testing.allocator in tests — detects memory leaks
  • camelCase functions, PascalCase types, SCREAMING_CASE constants
  • build.zig for builds — @cImport for C interop at boundaries only