Skip to content
Back to skills

Zig Best Practices

ASecurity

Use when reading or writing Zig files (.zig, build.zig, build.zig.zon).

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 16, 2026
ai-agentsbashtestingdebuggingapidocumentation

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add alleneubank/agent-profile --skill zig-best-practices --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Zig Best Practices?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Zig Best Practices
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/alleneubank-zig-best-practices/badge)](https://www.skillsdirectory.com/skills/alleneubank-zig-best-practices)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: zig-best-practices
description: Use when reading or writing Zig files (.zig, build.zig, build.zig.zon).
---

# Zig Best Practices

Follows the type, error, and assertion law in `code-law`. This skill covers Zig-specific idioms only.

## Type System Patterns

**Tagged unions for mutually exclusive states** — prevents invalid combinations that a struct with multiple nullable fields would allow:
```zig
const RequestState = union(enum) {
    idle,
    loading,
    success: []const u8,
    failure: anyerror,
};
```

**Explicit error sets** — documents exactly what can fail; `anyerror` hides failure modes:
```zig
const ParseError = error{ InvalidSyntax, UnexpectedToken, EndOfInput };
fn parse(input: []const u8) ParseError!Ast { ... }
```

**Distinct types for domain IDs** — compiler prevents mixing up different ID types:
```zig
const UserId = enum(u64) { _ };
const OrderId = enum(u64) { _ };
```

**Comptime validation** — catch invalid configurations at compile time, not runtime:
```zig
fn Buffer(comptime size: usize) type {
    if (size == 0) @compileError("buffer size must be greater than 0");
    return struct { data: [size]u8 = undefined, len: usize = 0 };
}
```

**Restrict who can do it** — when only one scope may perform an effect, keep its functions non-`pub` and expose only the scope that performs it; an opaque capability that the scope alone constructs makes an out-of-scope call a compile error. Correct types are the proof.

## Memory Management

- Pass allocators explicitly to every function that allocates; no global allocator state.
- Place `defer resource.deinit()` immediately after acquisition — keeps cleanup co-located with creation.
- Use `errdefer` for cleanup on error paths; `defer` for unconditional cleanup.
- Use arena allocators for batch/temporary work; they free everything at once.
- Use `std.testing.allocator` in tests — reports leaks with stack traces.

```zig
fn createResource(allocator: std.mem.Allocator) !*Resource {
    const resource = try allocator.create(Resource);
    errdefer allocator.destroy(resource);  // runs only on error
    resource.* = try initializeResource();
    return resource;
}
```

## Safety Idioms

Runs on the assertions-and-bounds law in `code-law`; these are the Zig-specific levers.

- `std.debug.assert` for invariants; `comptime` asserts check design relationships between constants (type sizes, layout invariants) before the program even runs.
- Prefer explicitly-sized integers (`u32`, `u64`) where the domain owns the type; keep `usize` at the std-lib seam (slice lengths, indices) instead of propagating it inward through `@intCast` chains.
- Decide allocation up front: arenas or fixed buffers sized at init for the data plane; every long-lived cache or pool carries an explicit upper bound.
- Construct large structs in place via an out-pointer (`fn init(target: *T) !void`) for pointer stability and no intermediate copy-moves; in-place init is viral — if a field needs it, the container does too.

## Key Conventions

- Prefer `const` over `var`; prefer slices over raw pointers.
- Prefer `comptime T: type` over `anytype`; explicit types produce clearer errors. Use `anytype` only for genuinely polymorphic cases (callbacks, `std.debug.print`-style).
- Exhaustive `switch`: include an `else` returning an error or `unreachable` for truly impossible cases.
- Use `std.log.scoped(.module_name)` for namespaced logging; define a module-level `const log` constant.
- Larger cohesive files are idiomatic — tests alongside implementation, comptime generics at file scope.

## Advanced Topics

- **Generic containers** (queues, stacks, trees): See [GENERICS.md](GENERICS.md)
- **C library interop** (raylib, SDL, curl): See [C-INTEROP.md](C-INTEROP.md)
- **Debugging memory leaks** (GPA, stack traces): See [DEBUGGING.md](DEBUGGING.md)

## Tooling

**zigdoc** — browse std library and dependency docs:
```bash
zigdoc std.mem.Allocator   # std lib symbol
zigdoc vaxis.Window        # project dependency
zigdoc @init               # create AGENTS.md with API patterns
```

**ziglint** — static analysis with `.ziglint.zon` config:
```bash
ziglint                    # lint current directory
ziglint --ignore Z001      # suppress specific rule
```

## References

- Language Reference: https://ziglang.org/documentation/0.15.2/
- Standard Library: https://ziglang.org/documentation/0.15.2/std/
- Zig Guide: https://zig.guide/

Files in this skill

  • C-INTEROP.md2 KB
  • DEBUGGING.md1.6 KB
  • GENERICS.md1.7 KB
  • SKILL.md4.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.