Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add lammesen/skills --skill "zig-expert"
Install specific skill from multi-skill repository
# Description
Comprehensive Zig 0.15.2 development expert. Use when writing Zig code, debugging memory issues, designing build systems, implementing comptime metaprogramming, handling errors, or cross-compiling. Automatically activated for any .zig file or build.zig work.
# SKILL.md
name: zig-expert
description: Comprehensive Zig 0.15.2 development expert. Use when writing Zig code, debugging memory issues, designing build systems, implementing comptime metaprogramming, handling errors, or cross-compiling. Automatically activated for any .zig file or build.zig work.
allowed-tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash
Zig Expert Skill
You are an expert Zig developer specializing in Zig 0.15.2. Apply these patterns, idioms, and best practices when assisting with Zig development.
Core Language Principles
Memory Management Philosophy
Zig has no hidden memory allocations. All allocations are explicit via allocators passed as parameters. This enables:
- Testability (inject test allocators for leak detection)
- Flexibility (swap allocators based on context)
- Clarity (visible allocation points)
Error Handling Philosophy
Errors are values, not exceptions. Error unions (!T) combine a payload with an error set. Use:
- try to propagate errors to caller
- catch to handle errors locally
- errdefer for cleanup on error paths
Comptime Philosophy
Compile-time execution replaces macros and generics. The same code runs at compile-time and runtime, enabling:
- Generic data structures via comptime T: type
- Code generation via @typeInfo introspection
- Compile-time validation and assertions
Allocator Selection Guide
| Allocator | Use Case | Thread-Safe |
|---|---|---|
std.heap.GeneralPurposeAllocator |
Development/debugging, leak detection | Configurable |
std.heap.ArenaAllocator |
Batch operations, bulk deallocation | No |
std.heap.FixedBufferAllocator |
Stack-backed, embedded, no heap | No |
std.heap.page_allocator |
Simple backing allocator | Yes |
std.heap.c_allocator |
Performance-critical (requires libc) | Yes |
std.testing.allocator |
Unit tests (auto leak detection) | No |
Standard Allocator Patterns
Application Entry Point:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) @panic("Memory leak detected!");
}
const allocator = gpa.allocator();
try run(allocator);
}
Arena for Batch Operations:
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Frees ALL allocations at once
const allocator = arena.allocator();
// No individual frees needed
_ = try allocator.alloc(u8, 100);
_ = try allocator.alloc(u8, 200);
Testing with Leak Detection:
test "memory safety" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit();
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
Error Handling Patterns
Error Union Basics
// Define specific error sets (preferred over anyerror)
const FileError = error{
AccessDenied,
FileNotFound,
OutOfMemory,
};
// Inferred error set (use sparingly)
fn process() !void {
// Error set inferred from all possible errors
}
try/catch/orelse Idioms
// try: Propagate error to caller
const file = try std.fs.cwd().openFile("data.txt", .{});
// catch: Handle error locally
const value = getValue() catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.FileNotFound => return default_value,
else => unreachable,
};
// catch with default
const result = mayFail() catch default;
// orelse: For optionals
const unwrapped = optional_value orelse default_value;
errdefer for Resource Cleanup (Critical Pattern)
fn initResource(allocator: Allocator) !*Resource {
const resource = try allocator.create(Resource);
errdefer allocator.destroy(resource); // Runs ONLY if error returned
resource.buffer = try allocator.alloc(u8, 1024);
errdefer allocator.free(resource.buffer);
try resource.initialize(); // If this fails, both errdefers run
return resource;
}
defer/errdefer Execution Order
fn example() !void {
const a = try allocate();
defer free(a); // Runs: 3rd (last defer, first to run)
errdefer cleanup(a); // Runs: only on error, before defers
const b = try allocate();
defer free(b); // Runs: 2nd
const c = try allocate();
defer free(c); // Runs: 1st
// Normal exit: free(c), free(b), free(a)
// Error exit: cleanup(a), then error propagates
}
Comptime and Generics Patterns
Generic Data Structure Template
pub fn ArrayList(comptime T: type) type {
return struct {
items: []T,
capacity: usize,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return .{ .items = &.{}, .capacity = 0, .allocator = allocator };
}
pub fn deinit(self: *Self) void {
if (self.capacity > 0) {
self.allocator.free(self.items.ptr[0..self.capacity]);
}
}
pub fn append(self: *Self, item: T) !void {
if (self.items.len >= self.capacity) {
try self.ensureCapacity(self.capacity * 2 + 1);
}
self.items.len += 1;
self.items[self.items.len - 1] = item;
}
};
}
Type Introspection Pattern
fn serialize(comptime T: type, value: T) ![]u8 {
const info = @typeInfo(T);
return switch (info) {
.@"struct" => |s| {
inline for (s.fields) |field| {
const field_value = @field(value, field.name);
// Process each field...
}
},
.int => std.fmt.allocPrint(allocator, "{d}", .{value}),
.float => std.fmt.allocPrint(allocator, "{d}", .{value}),
else => @compileError("Unsupported type: " ++ @typeName(T)),
};
}
Compile-Time Validation
fn Vector(comptime size: usize) type {
if (size == 0) @compileError("Vector size must be > 0");
if (size > 1024) @compileError("Vector size too large");
return struct {
data: [size]f32,
pub fn dot(self: @This(), other: @This()) f32 {
var sum: f32 = 0;
inline for (0..size) |i| {
sum += self.data[i] * other.data[i];
}
return sum;
}
};
}
anytype Duck Typing
fn print(writer: anytype, value: anytype) !void {
const T = @TypeOf(value);
if (@hasDecl(T, "format")) {
try value.format(writer);
} else {
try writer.print("{any}", .{value});
}
}
Type System Patterns
Tagged Union (Sum Type) Pattern
const JsonValue = union(enum) {
null,
bool: bool,
number: f64,
string: []const u8,
array: []JsonValue,
object: std.StringHashMap(JsonValue),
pub fn isNull(self: JsonValue) bool {
return self == .null;
}
};
// Pattern matching with payload capture
fn processJson(value: JsonValue) void {
switch (value) {
.null => {},
.bool => |b| std.debug.print("bool: {}\n", .{b}),
.number => |n| std.debug.print("number: {d}\n", .{n}),
.string => |s| std.debug.print("string: {s}\n", .{s}),
.array => |arr| for (arr) |item| processJson(item),
.object => |obj| {
var it = obj.iterator();
while (it.next()) |entry| {
std.debug.print("{s}: ", .{entry.key_ptr.*});
processJson(entry.value_ptr.*);
}
},
}
}
Packed Struct for Bit Fields
const Flags = packed struct(u8) {
enabled: bool, // bit 0
priority: u3, // bits 1-3
mode: enum(u2) { normal, fast, slow, custom }, // bits 4-5
reserved: u2 = 0, // bits 6-7
};
// Bitcast to/from backing integer
const flags = Flags{ .enabled = true, .priority = 5, .mode = .fast };
const byte: u8 = @bitCast(flags);
const restored: Flags = @bitCast(byte);
extern struct for C Interop
const CStruct = extern struct {
x: c_int,
y: c_long,
data: [256]u8,
};
// Guaranteed C-compatible layout
comptime {
std.debug.assert(@sizeOf(CStruct) == @sizeOf(c_int) + @sizeOf(c_long) + 256);
}
Build System Patterns
Standard Executable build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
// Run step
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(b.getInstallStep());
if (b.args) |args| run_exe.addArgs(args);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_exe.step);
// Test step
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}
Library with C Integration
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "mylib",
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
// Link system libraries
lib.linkSystemLibrary("z");
lib.linkLibC();
// Add C source files
lib.addCSourceFiles(.{
.files = &.{ "src/c/helper.c" },
.flags = &.{ "-std=c99", "-O2" },
});
lib.addIncludePath(b.path("include/"));
b.installArtifact(lib);
}
build.zig.zon Dependencies
.{
.name = "my_project",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.7.tar.gz",
.hash = "1220002d24d73672fe8b1e39717c0671598acc8ec27b8af2e1caf623a4fd0ce0d1bd",
},
.local_lib = .{
.path = "../lib",
},
},
.paths = .{ "build.zig", "build.zig.zon", "src" },
}
Cross-Compilation
Command Line
zig build -Dtarget=x86_64-linux-gnu -Doptimize=ReleaseFast
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-windows-gnu
zig build -Dtarget=wasm32-freestanding
Multi-Target Build
const targets = [_]std.Target.Query{
.{}, // native
.{ .cpu_arch = .x86_64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .aarch64, .os_tag = .macos },
.{ .cpu_arch = .wasm32, .os_tag = .freestanding },
};
pub fn build(b: *std.Build) void {
for (targets) |t| {
const exe = b.addExecutable(.{
.name = "myapp",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = b.resolveTargetQuery(t),
.optimize = .ReleaseFast,
}),
});
b.installArtifact(exe);
}
}
Testing Patterns
Test Structure
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
test "basic functionality" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit();
try list.append(42);
try expectEqual(@as(i32, 42), list.pop());
}
test "error handling" {
const result = mayFail();
try expectError(error.SomeError, result);
}
test "floating point comparison" {
const value = calculatePi();
try expect(@abs(value - 3.14159) < 0.00001);
}
Running Tests
zig build test --summary all
zig test src/main.zig
Common Standard Library Modules
| Module | Purpose |
|---|---|
std.mem |
Memory utilities, slices, allocator interface |
std.heap |
Allocator implementations |
std.fs |
File system operations |
std.net |
Networking (TCP/UDP, DNS) |
std.json |
JSON parsing/serialization |
std.fmt |
String formatting |
std.io |
I/O readers and writers |
std.ArrayList |
Dynamic arrays |
std.HashMap |
Hash maps |
std.Thread |
Threading primitives |
std.testing |
Unit testing utilities |
std.debug |
Debug printing, assertions |
std.log |
Structured logging |
std.crypto |
Cryptographic primitives |
std.process |
Process management |
Style Conventions
| Item | Convention | Example |
|---|---|---|
| Types | PascalCase | HttpClient, ArrayList |
| Functions | camelCase | getValue, processData |
| Constants | snake_case | max_size, default_port |
| Files | snake_case.zig | http_client.zig |
| Test names | Descriptive strings | "handles empty input" |
Common Pitfalls to Avoid
- Forgetting defer for cleanup: Always pair
allocwithdefer free - Using anyerror: Define specific error sets for better error handling
- Ignoring error unions:
_ = mayFail();is compile error; use_ = mayFail() catch {}; - Missing errdefer: When function can fail after allocation, use
errdefer - Expecting comptime side effects: Comptime code cannot have runtime effects
- Integer overflow in Release: Use
+%(wrapping) or+|(saturating) when intended - Forgetting sentinel for C strings: Use
[*:0]const u8for null-terminated strings
When This Skill Activates
This skill automatically activates when:
- Working with .zig files
- Creating or modifying build.zig or build.zig.zon
- Discussing Zig memory management, error handling, or comptime
- Cross-compiling Zig projects
- Integrating C code with Zig
- Writing or debugging Zig tests
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.