
Zig Project
- 70 installs
- 253 repo stars
- Updated August 4, 2026
- majiayu000/claude-arsenal
Helps with ai & agent building tasks during AI-assisted development.
About
zig-project is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- zig-project
- AI & Agent Building
- AI-coding skill
Zig Project by the numbers
- 70 all-time installs (skills.sh)
- Ranked #5,726 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/majiayu000/claude-arsenal --skill zig-projectAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 70 |
|---|---|
| repo stars | ★ 253 |
| Last updated | August 4, 2026 |
| Repository | majiayu000/claude-arsenal ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Zig Project Architecture
Core Principles
- No hidden behavior — No hidden allocations, no hidden control flow, no macros
- Explicit allocators — Pass allocator as parameter, never use global allocator
- Comptime over macros — Use comptime for generics and metaprogramming
- Error unions — Use
!Tfor explicit error handling, avoidanyerror - defer/errdefer — Resource cleanup at scope exit
- No backwards compatibility — Delete, don't deprecate. Change directly
- LiteLLM for LLM APIs — Use LiteLLM proxy for all LLM integrations
---
No Backwards Compatibility
Delete unused code. Change directly. No compatibility layers.
// ❌ BAD: Deprecated function kept around
/// Deprecated: Use newFunction instead
pub fn oldFunction() void {
@compileLog("oldFunction is deprecated");
newFunction();
}
// ❌ BAD: Alias for renamed functions
pub const old_name = new_name; // "for backwards compatibility"
// ❌ BAD: Unused parameters
fn process(_: *const Config, data: []const u8) !void {
_ = data;
}
// ✅ GOOD: Just delete and update all usages
pub fn newFunction() void {
// ...
}
// ✅ GOOD: Remove unused parameters entirely
fn process(data: []const u8) !void {
// ...
}---
LiteLLM for LLM APIs
Use LiteLLM proxy. Don't call provider APIs directly.
const std = @import("std");
const http = std.http;
pub const LLMClient = struct {
allocator: std.mem.Allocator,
base_url: []const u8,
api_key: []const u8,
pub fn init(allocator: std.mem.Allocator, base_url: []const u8, api_key: []const u8) LLMClient {
return .{
.allocator = allocator,
.base_url = base_url, // "http://localhost:4000"
.api_key = api_key,
};
}
pub fn complete(self: *LLMClient, prompt: []const u8, model: []const u8) ![]u8 {
// Use OpenAI-compatible API through LiteLLM proxy
var client = http.Client{ .allocator = self.allocator };
defer client.deinit();
// Build request to LiteLLM proxy...
_ = prompt;
_ = model;
return "";
}
};---
Quick Start
1. Initialize Project
# Create new project
mkdir myapp && cd myapp
zig init
# Or create executable project
zig init-exe
# Or create library project
zig init-lib2. Project Structure
myapp/
├── build.zig # Build configuration (in Zig)
├── build.zig.zon # Package manifest (dependencies)
├── src/
│ ├── main.zig # Entry point (for exe)
│ ├── root.zig # Library root (for lib)
│ └── lib/ # Internal modules
│ └── utils.zig
├── tests/ # Integration tests (optional)
└── lib/ # Vendored dependencies3. Core Files
build.zig.zon (Package Manifest)
.{
.name = "myapp",
.version = "0.1.0",
.dependencies = .{
// .some_dep = .{
// .url = "https://github.com/...",
// .hash = "...",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}build.zig (Build Script)
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_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// Run step
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
// Test step
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}---
Explicit Allocator Pattern
Core Principle
Every function that allocates must receive an allocator parameter.
const std = @import("std");
// ❌ BAD: Hidden allocation (don't do this)
var global_allocator: std.mem.Allocator = undefined;
fn badAlloc() ![]u8 {
return global_allocator.alloc(u8, 100);
}
// ✅ GOOD: Explicit allocator
fn goodAlloc(allocator: std.mem.Allocator) ![]u8 {
return allocator.alloc(u8, 100);
}Common Allocators
const std = @import("std");
pub fn main() !void {
// General purpose (with safety checks in debug)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Arena (bulk alloc/dealloc)
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const arena_alloc = arena.allocator();
// Fixed buffer (no heap)
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const fixed_alloc = fba.allocator();
// Page allocator (direct OS calls)
const page_alloc = std.heap.page_allocator;
_ = allocator;
_ = arena_alloc;
_ = fixed_alloc;
_ = page_alloc;
}Arena Pattern (Request-Scoped)
fn handleRequest(permanent_allocator: std.mem.Allocator) !void {
// Create arena for this request
var arena = std.heap.ArenaAllocator.init(permanent_allocator);
defer arena.deinit(); // Free ALL request memory at once
const allocator = arena.allocator();
// All allocations use arena - no individual frees needed
const data = try fetchData(allocator);
const processed = try processData(allocator, data);
try sendResponse(processed);
// arena.deinit() frees everything
}---
Error Handling
Error Unions
const std = @import("std");
// Define specific error set
const FileError = error{
NotFound,
AccessDenied,
OutOfMemory,
EndOfStream,
};
// Return error union
fn readFile(allocator: std.mem.Allocator, path: []const u8) FileError![]u8 {
const file = std.fs.cwd().openFile(path, .{}) catch |err| {
return switch (err) {
error.FileNotFound => FileError.NotFound,
error.AccessDenied => FileError.AccessDenied,
else => FileError.NotFound,
};
};
defer file.close();
return file.readToEndAlloc(allocator, 1024 * 1024) catch FileError.OutOfMemory;
}try / catch / errdefer
fn processFile(allocator: std.mem.Allocator, path: []const u8) !void {
// try: propagate error up
const data = try readFile(allocator, path);
errdefer allocator.free(data); // cleanup on error
// catch: handle error locally
const parsed = parseData(data) catch |err| {
std.log.err("Parse failed: {}", .{err});
return err;
};
try saveResult(parsed);
}Error Formatting
fn example() !void {
doSomething() catch |err| {
std.log.err("Operation failed: {s}", .{@errorName(err)});
return err;
};
}---
Comptime (Compile-Time Execution)
Generic Functions
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// Usage
const result = max(i32, 10, 20); // Returns 20
const float_result = max(f64, 1.5, 2.5); // Returns 2.5Generic Data Structures
pub fn ArrayList(comptime T: type) type {
return struct {
const Self = @This();
items: []T,
capacity: usize,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.items = &[_]T{},
.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 {
// Implementation...
_ = item;
}
};
}
// Usage
var list = ArrayList(u32).init(allocator);
defer list.deinit();Compile-Time Validation
fn validateConfig(comptime config: Config) void {
if (config.buffer_size == 0) {
@compileError("buffer_size must be > 0");
}
if (config.buffer_size > 1024 * 1024) {
@compileError("buffer_size too large");
}
}---
Testing
Inline Tests
const std = @import("std");
const testing = std.testing;
fn add(a: i32, b: i32) i32 {
return a + b;
}
test "add positive numbers" {
try testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "add negative numbers" {
try testing.expectEqual(@as(i32, -1), add(1, -2));
}Testing with Allocator
test "allocation test" {
// Use testing allocator for leak detection
const allocator = testing.allocator;
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
try testing.expect(data.len == 100);
}Testing Errors
test "expect error" {
const result = failingFunction();
try testing.expectError(error.SomeError, result);
}
test "expect no error" {
const result = try successFunction();
try testing.expect(result > 0);
}Run Tests
# Run all tests
zig build test
# Run tests with output
zig test src/main.zig
# Run specific test
zig test src/main.zig --test-filter "add positive"---
Common Commands
# Build
zig build # Debug build
zig build -Doptimize=ReleaseFast # Release build
# Run
zig build run # Build and run
# Test
zig build test # Run tests
# Format
zig fmt src/ # Format code
# Cross-compile
zig build -Dtarget=x86_64-linux-gnu
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-windows
# Use as C compiler
zig cc -o output input.c
zig c++ -o output input.cpp---
Extended Reference
Detailed material starting at ## Checklist has been moved to `reference/extended.md` to keep this skill concise. Load that reference when the task requires the moved examples, command catalogs, checklists, platform details, or implementation templates.
Zig Project Architecture
Project Layouts
Executable Project
myapp/
├── build.zig # Build configuration
├── build.zig.zon # Package manifest
├── src/
│ ├── main.zig # Entry point
│ ├── lib.zig # Internal library (optional)
│ └── utils/ # Sub-modules
│ ├── math.zig
│ └── io.zig
├── tests/ # Integration tests (optional)
│ └── integration.zig
└── lib/ # Vendored C libraries (optional)
└── stb/Library Project
mylib/
├── build.zig
├── build.zig.zon
├── src/
│ ├── root.zig # Library root (exports)
│ ├── core.zig
│ └── internal/ # Private modules
│ └── helpers.zig
├── tests/
│ └── lib_test.zig
└── examples/
└── basic.zigApplication with Multiple Binaries
project/
├── build.zig
├── build.zig.zon
├── src/
│ ├── lib/ # Shared library code
│ │ ├── root.zig
│ │ └── core.zig
│ ├── server/ # Server binary
│ │ └── main.zig
│ ├── client/ # Client binary
│ │ └── main.zig
│ └── cli/ # CLI tool
│ └── main.zig
└── tests/---
Module System
File = Module
Each .zig file is a module. No explicit module declarations needed.
// src/main.zig
const std = @import("std");
const utils = @import("utils/math.zig"); // Import from path
pub fn main() !void {
const result = utils.add(1, 2);
std.debug.print("Result: {}\n", .{result});
}// src/utils/math.zig
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
// Private function (not exported)
fn helper() void {}Root Module Pattern
// src/root.zig (library root)
pub const core = @import("core.zig");
pub const utils = @import("utils.zig");
// Re-export commonly used items
pub const Config = core.Config;
pub const Error = core.Error;
// Top-level functions
pub fn init(allocator: std.mem.Allocator) !*Context {
return core.Context.init(allocator);
}@import vs usingnamespace
// Prefer explicit imports
const http = @import("http.zig");
const response = http.Response;
// Avoid usingnamespace in public APIs
// Only use for private convenience
const Self = @This();---
Build System
Basic build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
// Allow user to override target and optimize
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Main executable
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Link system libraries if needed
exe.linkSystemLibrary("c");
exe.linkSystemLibrary("ssl");
b.installArtifact(exe);
// Run step
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
// Test step
const unit_tests = b.addTest(.{
.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 Build
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Static library
const lib = b.addStaticLibrary(.{
.name = "mylib",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// Also create module for use as dependency
_ = b.addModule("mylib", .{
.root_source_file = b.path("src/root.zig"),
});
}Multiple Targets
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Server
const server = b.addExecutable(.{
.name = "server",
.root_source_file = b.path("src/server/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(server);
// Client
const client = b.addExecutable(.{
.name = "client",
.root_source_file = b.path("src/client/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(client);
// CLI
const cli = b.addExecutable(.{
.name = "cli",
.root_source_file = b.path("src/cli/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(cli);
// Shared library module
const lib_mod = b.addModule("lib", .{
.root_source_file = b.path("src/lib/root.zig"),
});
server.root_module.addImport("lib", lib_mod);
client.root_module.addImport("lib", lib_mod);
cli.root_module.addImport("lib", lib_mod);
}---
Package Manifest (build.zig.zon)
Basic Manifest
.{
.name = "myapp",
.version = "0.1.0",
// Minimum Zig version required
.minimum_zig_version = "0.13.0",
.dependencies = .{},
// Files to include in package
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"LICENSE",
"README.md",
},
}With Dependencies
.{
.name = "myapp",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.3.0.tar.gz",
.hash = "1220aabbccdd...",
},
.known_folders = .{
.url = "git+https://github.com/ietf-wg-masque/draft-ietf-masque-connect-ip#v0.1.0",
.hash = "1220...",
},
// Local path dependency
.mylib = .{
.path = "../mylib",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}Using Dependencies in build.zig
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Get dependency
const zap = b.dependency("zap", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Add dependency module
exe.root_module.addImport("zap", zap.module("zap"));
b.installArtifact(exe);
}---
TigerBeetle-Style Architecture
For high-performance, safety-critical systems:
project/
├── build.zig
├── build.zig.zon
├── src/
│ ├── main.zig # Entry point
│ ├── state_machine.zig # Core FSM
│ ├── io.zig # I/O layer (io_uring)
│ ├── storage.zig # Storage engine
│ ├── network.zig # Network layer
│ ├── config.zig # Static configuration
│ └── types.zig # Fixed-size types
├── scripts/
│ └── benchmark.zig
└── clients/ # Language bindings
├── zig/
└── c/Key Principles
// types.zig - Fixed size, cache-aligned
pub const Transfer = extern struct {
id: u128,
debit_account: u128,
credit_account: u128,
amount: u64,
timestamp: u64,
// Exactly 128 bytes, cache-line aligned
comptime {
std.debug.assert(@sizeOf(Transfer) == 128);
std.debug.assert(@alignOf(Transfer) == 64);
}
};
// Static memory allocation
var transfers: [MAX_TRANSFERS]Transfer = undefined;
var transfer_count: usize = 0;---
Cross-Compilation
From build.zig
pub fn build(b: *std.Build) void {
// Let user override, with defaults
const target = b.standardTargetOptions(.{
.default_target = .{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .gnu,
},
});
// ...
}From Command Line
# Linux x86_64
zig build -Dtarget=x86_64-linux-gnu
# macOS ARM64
zig build -Dtarget=aarch64-macos
# Windows x86_64
zig build -Dtarget=x86_64-windows-msvc
# FreeBSD
zig build -Dtarget=x86_64-freebsd
# Cross-compile C code
zig cc -target aarch64-linux-gnu -o output input.c---
Test Organization
Inline Tests (Recommended)
// src/math.zig
const std = @import("std");
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
// Tests are in the same file
test "add works" {
try std.testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "divide by zero returns error" {
try std.testing.expectError(error.DivisionByZero, divide(10, 0));
}
test "divide works" {
try std.testing.expectEqual(@as(i32, 5), try divide(10, 2));
}Separate Test Files
// tests/integration.zig
const std = @import("std");
const mylib = @import("mylib");
test "full integration test" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var app = try mylib.App.init(allocator);
defer app.deinit();
try app.run();
}Add to build.zig:
const integration_tests = b.addTest(.{
.root_source_file = b.path("tests/integration.zig"),
.target = target,
.optimize = optimize,
});
integration_tests.root_module.addImport("mylib", lib_mod);
const run_integration = b.addRunArtifact(integration_tests);
const integration_step = b.step("test-integration", "Run integration tests");
integration_step.dependOn(&run_integration.step);zig-project Extended Reference
This file preserves detailed material moved out of SKILL.md for progressive disclosure. Load it only when the current task needs the specific examples, commands, templates, or checklists below.
Moved content starts at: ## Checklist.
Checklist
## Project Setup
- [ ] build.zig configured
- [ ] build.zig.zon with metadata
- [ ] Source in src/ directory
## Architecture
- [ ] Explicit allocators everywhere
- [ ] No global state
- [ ] Error sets defined
- [ ] errdefer for cleanup
## Quality
- [ ] Tests with std.testing
- [ ] Memory leak detection in tests
- [ ] zig fmt applied
- [ ] Comptime validation where appropriate
## Build
- [ ] Debug and Release configs
- [ ] Cross-compilation targets
- [ ] Test step defined---
See Also
- reference/architecture.md — Project structure patterns
- reference/tech-stack.md — Libraries and tools
- reference/patterns.md — Zig idioms and patterns
Zig Design Patterns
Explicit Allocator Pattern
The fundamental Zig pattern: pass allocators explicitly.
Basic Usage
const std = @import("std");
pub fn createBuffer(allocator: std.mem.Allocator, size: usize) ![]u8 {
return allocator.alloc(u8, size);
}
pub fn freeBuffer(allocator: std.mem.Allocator, buffer: []u8) void {
allocator.free(buffer);
}
// Usage
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const buffer = try createBuffer(allocator, 1024);
defer freeBuffer(allocator, buffer);
// Use buffer...
}Struct with Allocator
const std = @import("std");
pub const ArrayList = struct {
allocator: std.mem.Allocator,
items: []u8,
capacity: usize,
pub fn init(allocator: std.mem.Allocator) ArrayList {
return .{
.allocator = allocator,
.items = &[_]u8{},
.capacity = 0,
};
}
pub fn deinit(self: *ArrayList) void {
if (self.capacity > 0) {
self.allocator.free(self.items.ptr[0..self.capacity]);
}
self.* = undefined;
}
pub fn append(self: *ArrayList, item: u8) !void {
if (self.items.len >= self.capacity) {
try self.grow();
}
self.items.ptr[self.items.len] = item;
self.items.len += 1;
}
fn grow(self: *ArrayList) !void {
const new_cap = if (self.capacity == 0) 8 else self.capacity * 2;
const new_mem = try self.allocator.realloc(
self.items.ptr[0..self.capacity],
new_cap,
);
self.items.ptr = new_mem.ptr;
self.capacity = new_cap;
}
};---
Arena Allocator Pattern
Bulk allocation with single deallocation. Perfect for request-scoped work.
const std = @import("std");
fn handleRequest(base_allocator: std.mem.Allocator, request: Request) !Response {
// Create arena for this request
var arena = std.heap.ArenaAllocator.init(base_allocator);
defer arena.deinit(); // Frees ALL allocations at once
const allocator = arena.allocator();
// All allocations use arena - no individual frees needed
const parsed = try parseRequest(allocator, request);
const result = try processData(allocator, parsed);
const response = try formatResponse(allocator, result);
return response;
// arena.deinit() cleans up everything
}Nested Arenas
fn processLargeDataset(allocator: std.mem.Allocator, data: []const Item) !Result {
var results = std.ArrayList(ItemResult).init(allocator);
defer results.deinit();
for (data) |item| {
// Inner arena for each item
var item_arena = std.heap.ArenaAllocator.init(allocator);
defer item_arena.deinit();
const item_alloc = item_arena.allocator();
const processed = try processItem(item_alloc, item);
try results.append(processed.toResult());
// item_arena freed, but results kept
}
return results.toOwnedSlice();
}---
Error Handling Patterns
Specific Error Sets
// Define specific errors for your domain
pub const ConfigError = error{
FileNotFound,
ParseError,
InvalidValue,
MissingField,
};
pub const NetworkError = error{
ConnectionFailed,
Timeout,
ProtocolError,
};
// Combine error sets
pub const AppError = ConfigError || NetworkError || error{OutOfMemory};
fn loadConfig(path: []const u8) ConfigError!Config {
// ...
}
fn connect(host: []const u8) NetworkError!Connection {
// ...
}errdefer for Cleanup
fn createResource(allocator: std.mem.Allocator) !*Resource {
const resource = try allocator.create(Resource);
errdefer allocator.destroy(resource); // Cleanup on error
resource.buffer = try allocator.alloc(u8, 1024);
errdefer allocator.free(resource.buffer);
resource.handle = try openHandle();
errdefer closeHandle(resource.handle);
try resource.initialize();
return resource; // Success - no errdefer triggered
}Error Payload (Zig 0.14+)
const ValidationError = error{
InvalidField,
MissingRequired,
};
fn validate(data: Data) ValidationError!void {
if (data.name.len == 0) {
// Can't attach payload in stable Zig yet
// Future: return error.MissingRequired.{ .field = "name" };
return error.MissingRequired;
}
}---
Comptime Patterns
Generic Container
pub fn BoundedArray(comptime T: type, comptime capacity: usize) type {
return struct {
const Self = @This();
buffer: [capacity]T = undefined,
len: usize = 0,
pub fn append(self: *Self, item: T) !void {
if (self.len >= capacity) {
return error.Overflow;
}
self.buffer[self.len] = item;
self.len += 1;
}
pub fn slice(self: *Self) []T {
return self.buffer[0..self.len];
}
};
}
// Usage
var arr = BoundedArray(u32, 100){};
try arr.append(42);Type Introspection
fn printFields(comptime T: type, value: T) void {
const info = @typeInfo(T);
switch (info) {
.Struct => |s| {
inline for (s.fields) |field| {
std.debug.print("{s}: {}\n", .{
field.name,
@field(value, field.name),
});
}
},
else => @compileError("Expected struct"),
}
}
const Point = struct { x: i32, y: i32 };
printFields(Point, .{ .x = 10, .y = 20 });
// Output: x: 10
// y: 20Compile-Time Validation
fn ensureValidConfig(comptime config: Config) void {
if (config.buffer_size == 0) {
@compileError("buffer_size must be > 0");
}
if (config.max_connections > 10000) {
@compileError("max_connections too high");
}
}
pub fn Server(comptime config: Config) type {
ensureValidConfig(config);
return struct {
// Server implementation using config
};
}Compile-Time String Operations
fn comptimeConcat(comptime a: []const u8, comptime b: []const u8) []const u8 {
return a ++ b;
}
const greeting = comptimeConcat("Hello, ", "World!");
// greeting is "Hello, World!" at compile time---
defer Pattern
Resource Cleanup
fn processFile(path: []const u8) !void {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close(); // Always runs
const buffer = try allocator.alloc(u8, 4096);
defer allocator.free(buffer);
// Use file and buffer...
// Both cleaned up when function exits
}Multiple defers (LIFO order)
fn example() void {
defer std.debug.print("3\n", .{});
defer std.debug.print("2\n", .{});
defer std.debug.print("1\n", .{});
}
// Output: 1, 2, 3 (reverse order)defer vs errdefer
fn createPair(allocator: std.mem.Allocator) !Pair {
const a = try allocator.create(A);
errdefer allocator.destroy(a); // Only on error
const b = try allocator.create(B);
// No errdefer needed - a will be in returned Pair
return Pair{ .a = a, .b = b };
}---
Iterator Pattern
Simple Iterator
const Iterator = struct {
data: []const u8,
index: usize = 0,
pub fn next(self: *Iterator) ?u8 {
if (self.index >= self.data.len) return null;
defer self.index += 1;
return self.data[self.index];
}
};
// Usage
var iter = Iterator{ .data = "hello" };
while (iter.next()) |char| {
std.debug.print("{c}", .{char});
}Generic Iterator
pub fn SliceIterator(comptime T: type) type {
return struct {
const Self = @This();
slice: []const T,
index: usize = 0,
pub fn next(self: *Self) ?T {
if (self.index >= self.slice.len) return null;
defer self.index += 1;
return self.slice[self.index];
}
pub fn reset(self: *Self) void {
self.index = 0;
}
};
}---
Interface Pattern (Duck Typing)
Zig uses comptime duck typing instead of interfaces.
fn serialize(writer: anytype, data: anytype) !void {
// writer must have a write method
try writer.writeAll(@typeName(@TypeOf(data)));
try writer.writeAll(": ");
const info = @typeInfo(@TypeOf(data));
switch (info) {
.Int => try writer.print("{d}", .{data}),
.Float => try writer.print("{d:.2}", .{data}),
.Pointer => |ptr| {
if (ptr.child == u8) {
try writer.writeAll(data);
}
},
else => try writer.writeAll("(unknown)"),
}
}
// Works with any type that has writeAll and print
var buffer: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buffer);
try serialize(stream.writer(), 42);
try serialize(stream.writer(), "hello");---
Sentinel-Terminated Arrays
// Null-terminated string (C compatible)
const c_string: [:0]const u8 = "hello";
// Custom sentinel
const arr: [5:0]u8 = .{ 1, 2, 3, 4, 5 };
// arr[5] == 0 (sentinel)
// Convert to C pointer
fn toCString(s: [:0]const u8) [*:0]const u8 {
return s.ptr;
}---
Optional Pattern
fn find(haystack: []const u8, needle: u8) ?usize {
for (haystack, 0..) |c, i| {
if (c == needle) return i;
}
return null;
}
// Usage
if (find("hello", 'l')) |index| {
std.debug.print("Found at {}\n", .{index});
} else {
std.debug.print("Not found\n", .{});
}
// With orelse
const index = find("hello", 'x') orelse 0;
// Unwrap (panic if null)
const index2 = find("hello", 'l').?;---
Tagged Union Pattern
const Value = union(enum) {
int: i64,
float: f64,
string: []const u8,
none,
pub fn format(
self: Value,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
switch (self) {
.int => |v| try writer.print("{d}", .{v}),
.float => |v| try writer.print("{d:.2}", .{v}),
.string => |v| try writer.print("\"{s}\"", .{v}),
.none => try writer.writeAll("null"),
}
}
};
// Usage
const values = [_]Value{
.{ .int = 42 },
.{ .float = 3.14 },
.{ .string = "hello" },
.none,
};
for (values) |v| {
std.debug.print("{}\n", .{v});
}---
Summary Table
| Pattern | Use Case |
|---|---|
| Explicit Allocator | All memory allocation |
| Arena Allocator | Request/phase-scoped memory |
| errdefer | Cleanup on error paths |
| Comptime Generics | Type-safe containers |
| Type Introspection | Serialization, debugging |
| defer | Resource cleanup |
| Iterator | Sequential access |
| Duck Typing | Generic functions |
| Tagged Union | Sum types, variants |
| Optional | Nullable values |
Zig Tech Stack
Version Strategy
Use latest stable Zig. Update when stable releases.
Zig is still pre-1.0, so tracking releases is important:
- Check ziglang.org for latest
- Version in
build.zig.zonwithminimum_zig_version
---
Core Tools
Zig Compiler
# Build
zig build # Debug build
zig build -Doptimize=ReleaseFast # Release (speed)
zig build -Doptimize=ReleaseSafe # Release (with safety)
zig build -Doptimize=ReleaseSmall # Release (size)
# Run
zig build run
# Test
zig build test
# Format
zig fmt src/
# Direct compilation
zig build-exe src/main.zig
zig build-lib src/lib.zig
zig run src/main.zig
zig test src/main.zigZig as C/C++ Compiler
# Compile C
zig cc -o output input.c
zig cc -target aarch64-linux-gnu input.c
# Compile C++
zig c++ -o output input.cpp
# With flags
zig cc -O3 -march=native -o output input.cZLS (Zig Language Server)
# Install
# Download from https://github.com/zigtools/zls/releases
# Configure in editor (VS Code, Neovim, etc.)
# Provides: completion, diagnostics, go-to-definition---
Package Management
Built-in Package Manager
// build.zig.zon
.{
.name = "myapp",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/v0.3.0.tar.gz",
.hash = "1220...",
},
},
}Fetch Dependencies
# Fetch and show hash
zig fetch https://github.com/zigzap/zap/archive/v0.3.0.tar.gz
# Update all dependencies
zig build --fetchAlternative: zigmod
# Install zigmod
# Add dependencies in zig.mod file
zigmod fetchPackage Registries
- Zigistry: https://zigistry.dev/
- zig.pm: Community packages
---
Popular Libraries
Web/HTTP
| Library | Description | URL |
|---|---|---|
| zap | Blazingly fast web framework | github.com/zigzap/zap |
| http.zig | HTTP client/server | github.com/karlseguin/http.zig |
| zzz | HTTP server | github.com/supersayen/zzz |
Async I/O
| Library | Description |
|---|---|
| std.io | Built-in async I/O |
| io_uring | Linux kernel interface (used by TigerBeetle) |
Data Structures
| Library | Description |
|---|---|
| std.ArrayList | Dynamic array |
| std.HashMap | Hash map |
| std.BoundedArray | Fixed-capacity array |
| std.PriorityQueue | Priority queue |
JSON
const std = @import("std");
const json = std.json;
// Parse JSON
const parsed = try json.parseFromSlice(MyStruct, allocator, json_string, .{});
defer parsed.deinit();
// Stringify
var buffer: [1024]u8 = undefined;
var stream = std.io.fixedBufferStream(&buffer);
try json.stringify(my_struct, .{}, stream.writer());Compression
const std = @import("std");
// DEFLATE
const deflate = std.compress.deflate;
const gzip = std.compress.gzip;
const zlib = std.compress.zlib;Crypto
const std = @import("std");
const crypto = std.crypto;
// Hashing
const hash = crypto.hash.sha2.Sha256;
var h = hash.init(.{});
h.update(data);
const digest = h.finalResult();
// Random
var prng = std.rand.DefaultPrng.init(seed);
const random = prng.random();---
C Interop
Linking C Libraries
// build.zig
exe.linkSystemLibrary("c");
exe.linkSystemLibrary("ssl");
exe.linkSystemLibrary("crypto");
// Add include path
exe.addIncludePath(b.path("include"));
exe.addCSourceFile(.{
.file = b.path("src/wrapper.c"),
.flags = &.{"-Wall", "-O2"},
});Calling C from Zig
const c = @cImport({
@cInclude("stdio.h");
@cInclude("mylib.h");
});
pub fn main() void {
_ = c.printf("Hello from C\n");
c.my_c_function();
}Exposing Zig to C
// Export function with C ABI
export fn add(a: c_int, b: c_int) c_int {
return a + b;
}
// Generate header
// zig build-lib src/lib.zig -femit-h---
Testing Tools
std.testing
const std = @import("std");
const testing = std.testing;
test "basic assertions" {
try testing.expect(true);
try testing.expectEqual(@as(i32, 5), 5);
try testing.expectEqualStrings("hello", "hello");
try testing.expectError(error.SomeError, failingFn());
}
test "memory leak detection" {
// testing.allocator detects leaks
const allocator = testing.allocator;
const ptr = try allocator.alloc(u8, 100);
defer allocator.free(ptr); // Must free or test fails
}
test "approximate equality" {
try testing.expectApproxEqAbs(@as(f32, 1.0), 1.0001, 0.001);
}Test Filtering
# Run specific test
zig test src/main.zig --test-filter "my test name"
# Run tests matching pattern
zig test src/main.zig --test-filter "parse"---
Debugging
Debug Mode
// Enabled in Debug builds
std.debug.print("Value: {}\n", .{value});
std.debug.assert(condition);
// Stack traces on panic
@panic("Something went wrong");
// Breakpoint
@breakpoint();Using GDB/LLDB
# Build with debug info (default)
zig build
# Debug
gdb ./zig-out/bin/myapp
lldb ./zig-out/bin/myappSafety Checks
// In Debug and ReleaseSafe:
// - Array bounds checking
// - Integer overflow detection
// - Null pointer checks
// - Use-after-free detection (with testing.allocator)
const arr = [_]i32{ 1, 2, 3 };
_ = arr[10]; // Panic in safe modes---
Build Optimization
Optimize Modes
| Mode | Speed | Safety | Size |
|---|---|---|---|
| Debug | Slow | Full | Large |
| ReleaseSafe | Fast | Full | Medium |
| ReleaseFast | Fastest | None | Medium |
| ReleaseSmall | Fast | None | Smallest |
Profile-Guided Optimization
# Generate profile
zig build -Doptimize=ReleaseFast
./zig-out/bin/myapp # Run with real workload
# Build with PGO (if using LLVM backend)
# Zig's self-hosted backend doesn't support PGO yet---
Cross-Platform Targets
Supported Targets
# List all targets
zig targets
# Common targets
x86_64-linux-gnu
x86_64-linux-musl
aarch64-linux-gnu
x86_64-macos
aarch64-macos
x86_64-windows-msvc
x86_64-windows-gnu
wasm32-wasiCross-Compile Example
# Build for all platforms from any machine
zig build -Dtarget=x86_64-linux-musl # Static Linux
zig build -Dtarget=aarch64-macos # Apple Silicon
zig build -Dtarget=x86_64-windows-gnu # Windows---
IDE Support
VS Code
- Extension: Zig Language (ziglang.vscode-zig)
- Requires ZLS installed
Neovim
- Plugin: nvim-lspconfig with ZLS
- zig.vim for syntax
Other Editors
- Emacs: zig-mode
- Sublime: Zig package
- IntelliJ: Zig plugin (community)
const std = @import("std");
pub fn build(b: *std.Build) void {
// Allow user to override target and optimize
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Main executable
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Link system libraries if needed
// exe.linkSystemLibrary("c");
b.installArtifact(exe);
// Run step: `zig build run`
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
// Pass arguments: `zig build run -- arg1 arg2`
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
// Test step: `zig build test`
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
// Library tests
const lib_tests = b.addTest(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
const run_lib_tests = b.addRunArtifact(lib_tests);
test_step.dependOn(&run_lib_tests.step);
}
.{
.name = "myapp",
.version = "0.1.0",
// Minimum Zig version required
.minimum_zig_version = "0.13.0",
.dependencies = .{
// Add dependencies here:
// .some_dep = .{
// .url = "https://github.com/user/repo/archive/refs/tags/v1.0.0.tar.gz",
// .hash = "1220...",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
const std = @import("std");
/// Error set for library operations
pub const LibError = error{
InvalidInput,
FormatError,
};
/// Creates a greeting string. Caller owns returned memory.
pub fn createGreeting(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
if (name.len == 0) {
return LibError.InvalidInput;
}
return std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
}
/// Example struct with explicit allocator
pub const Buffer = struct {
allocator: std.mem.Allocator,
data: []u8,
pub fn init(allocator: std.mem.Allocator, size: usize) !Buffer {
const data = try allocator.alloc(u8, size);
return .{
.allocator = allocator,
.data = data,
};
}
pub fn deinit(self: *Buffer) void {
self.allocator.free(self.data);
self.* = undefined;
}
pub fn fill(self: *Buffer, value: u8) void {
@memset(self.data, value);
}
};
// Tests
test "createGreeting with valid name" {
const allocator = std.testing.allocator;
const greeting = try createGreeting(allocator, "Zig");
defer allocator.free(greeting);
try std.testing.expectEqualStrings("Hello, Zig!", greeting);
}
test "createGreeting with empty name returns error" {
const allocator = std.testing.allocator;
const result = createGreeting(allocator, "");
try std.testing.expectError(LibError.InvalidInput, result);
}
test "Buffer init and deinit" {
const allocator = std.testing.allocator;
var buffer = try Buffer.init(allocator, 1024);
defer buffer.deinit();
try std.testing.expect(buffer.data.len == 1024);
}
test "Buffer fill" {
const allocator = std.testing.allocator;
var buffer = try Buffer.init(allocator, 4);
defer buffer.deinit();
buffer.fill(0xAB);
try std.testing.expectEqualSlices(u8, &[_]u8{ 0xAB, 0xAB, 0xAB, 0xAB }, buffer.data);
}
const std = @import("std");
const lib = @import("lib.zig");
pub fn main() !void {
// Use GeneralPurposeAllocator for memory allocation
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Get stdout writer
const stdout = std.io.getStdOut().writer();
// Example: Use library function
const greeting = try lib.createGreeting(allocator, "World");
defer allocator.free(greeting);
try stdout.print("{s}\n", .{greeting});
}
test "main runs without error" {
// Basic smoke test
const allocator = std.testing.allocator;
const greeting = try lib.createGreeting(allocator, "Test");
defer allocator.free(greeting);
try std.testing.expectEqualStrings("Hello, Test!", greeting);
}