zig

Installation
SKILL.md

Zig Language Reference (v0.16.0)

Zig evolves rapidly. Training data contains outdated patterns that cause compilation errors. This skill documents breaking changes and correct modern patterns.

Version coverage: 0.16.0 (current) with migration notes from 0.15.x and 0.14.x.

Design Principles

Type-First Development

Define types and function signatures before implementation. Let the compiler guide completeness:

  1. Define data structures (structs, unions, error sets)
  2. Define function signatures (parameters, return types, error unions)
  3. Implement to satisfy types
  4. Validate at compile-time

Make Illegal States Unrepresentable

Use Zig's type system to prevent invalid states at compile time:

  • Tagged unions over structs with optional fields — prevent impossible state combinations
  • Explicit error sets over anyerror — document exactly which failures can occur
  • Distinct types via enum(u64) { _ } — prevent mixing up IDs (user_id vs order_id)
  • Comptime validation with @compileError() — catch invalid configurations at build time

Module Structure

Larger cohesive files are idiomatic in Zig. Keep related code together — tests alongside implementation, comptime generics at file scope, visibility controlled by pub. Split files only for genuinely separate concerns. The std library demonstrates this with files like std/mem.zig containing thousands of cohesive lines.

Memory Ownership

  • Pass allocators explicitly — never use global state for allocation
  • Use defer immediately after acquiring a resource — cleanup next to acquisition
  • Name allocators by contract: gpa (caller must free), arena (bulk-free at boundary), scratch (never escapes)
  • Prefer const over var — immutability signals intent and enables optimizations
  • Prefer slices over raw pointers — bounds safety

Critical: Removed Features (0.15.x)

usingnamespace - REMOVED

// WRONG - compile error
pub usingnamespace @import("other.zig");

// CORRECT - explicit re-export
const other = @import("other.zig");
pub const foo = other.foo;

async/await - REMOVED

Keywords removed from language. Async I/O support is planned for future releases.

std.BoundedArray - REMOVED

Use std.ArrayList with initBuffer:

var buffer: [8]i32 = undefined;
var stack = std.ArrayList(i32).initBuffer(&buffer);

std.RingBuffer, std.fifo.LinearFifo - REMOVED

Use std.Io.Reader/std.Io.Writer ring buffers instead.

std.io.SeekableStream, std.io.BitReader, std.io.BitWriter - REMOVED

std.fmt.Formatter - REMOVED

Replaced by std.fmt.Alt.

Undefined Behavior Restrictions (0.15.x)

Arithmetic on undefined is now illegal. Only operators that can never trigger Illegal Behavior permit undefined as operand.

// WRONG - compile error in 0.15.x
var n: usize = undefined;
while (condition) : (n += 1) {}  // ERROR: use of undefined value

// CORRECT - explicit initialization required
var n: usize = 0;
while (condition) : (n += 1) {}

// OK - space reservation (no arithmetic)
var buffer: [256]u8 = undefined;

Critical: Networking Removed — std.netstd.Io.net (0.16)

std.net is completely removed in 0.16. Replaced by std.Io.net, which requires an Io instance.

Accept Loop

// WRONG (0.15) — std.net removed
const addr = std.net.Address.parseIp4(host, port) catch unreachable;
var server = addr.listen(.{ .reuse_address = true }) catch unreachable;
const conn = server.accept() catch continue;
defer conn.stream.close();

// CORRECT (0.16) — std.Io.net with Io instance
const addr = try std.Io.net.IpAddress.parse(host, port);
var server = try addr.listen(io, .{ .reuse_address = true });
const stream = try server.accept();  // returns Stream directly, no .stream wrapper
defer stream.close(io);              // close() now takes io

Io Runtime Setup

// Create Io instance at startup, thread it through your program
var threaded = std.Io.Threaded.init(std.heap.c_allocator);
var io: std.Io = threaded.io();

Stream Changes

// stream.handle → stream.socket.handle
std.posix.setsockopt(stream.socket.handle, ...);

// Io.net.Stream has NO .read() or .writeAll() — use raw C calls for blocking I/O:
extern "c" fn write(fd: c_int, buf: [*]const u8, n: usize) isize;

fn writeAll(stream: std.Io.net.Stream, data: []const u8) !void {
    var rem = data;
    while (rem.len > 0) {
        const n = write(stream.socket.handle, rem.ptr, rem.len);
        if (n <= 0) return error.BrokenPipe;
        rem = rem[@intCast(n)..];
    }
}
// std.posix.read() still works for reading

Removed Convenience Functions

// connectUnixSocket, tcpConnectToHost — removed, use C externs:
extern "c" fn socket(domain: c_int, typ: c_int, proto: c_int) c_int;
extern "c" fn connect(fd: c_int, addr: *const anyopaque, len: u32) c_int;
// IMPORTANT: don't name local variables "socket" or "connect" — shadows extern

// std.net.has_unix_sockets → std.Io.net.has_unix_sockets
// std.posix.close → std.c.close  (posix.close removed)
// std.posix.write/connect/socket — removed, use std.c.* or extern "c"

See std.net reference for complete networking documentation.

Critical: Time APIs Removed (0.16)

std.time.timestamp(), milliTimestamp(), microTimestamp(), nanoTimestamp() are removed. Use std.c.clock_gettime:

// WRONG (0.16) — removed
const secs = std.time.timestamp();
const ms = std.time.milliTimestamp();

// CORRECT — clock_gettime replacement
fn timestampSec() i64 {
    var ts: std.c.timespec = undefined;
    _ = std.c.clock_gettime(.REALTIME, &ts);
    return ts.sec;
}

fn milliTimestamp() i64 {
    var ts: std.c.timespec = undefined;
    _ = std.c.clock_gettime(.REALTIME, &ts);
    return @as(i64, ts.sec) * 1000 + @divTrunc(@as(i64, ts.nsec), 1_000_000);
}

Note: ts.nsec is signed — use @divTrunc, not / (0.16 enforces this for signed division).

std.time.ns_per_s, Instant, Timerstill present.

Critical: Thread Primitives Removed (0.16)

std.Thread.Mutex, std.Thread.Condition, std.Thread.sleep are removed. The 0.16 replacements (std.Io.Mutex/std.Io.Condition) require an Io instance. For library code without Io, use POSIX shims:

// WRONG (0.16)
var mutex: std.Thread.Mutex = .{};
mutex.lock();

// CORRECT — pthread shim (works without Io)
const PthreadMutex = struct {
    inner: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER,
    pub fn lock(m: *@This()) void { _ = std.c.pthread_mutex_lock(&m.inner); }
    pub fn unlock(m: *@This()) void { _ = std.c.pthread_mutex_unlock(&m.inner); }
    pub fn tryLock(m: *@This()) bool {
        return @intFromEnum(std.c.pthread_mutex_trylock(&m.inner)) == 0;
    }
};

// std.Thread.sleep → nanosleep
fn threadSleep(ns: u64) void {
    const ts = std.c.timespec{
        .sec = @intCast(ns / std.time.ns_per_s),
        .nsec = @intCast(ns % std.time.ns_per_s),
    };
    _ = std.c.nanosleep(&ts, null);
}

std.Thread.spawnunchanged.

See std.Thread reference for complete threading documentation including PthreadCondition.

Critical: Debug Stderr Changed (0.16)

// WRONG (0.16) — lockStderrWriter removed
const stderr = std.debug.lockStderrWriter();
defer std.debug.unlockStderr();
try stderr.print("msg\n", .{});

// CORRECT — lockStderr with buffer
var buf: [4096]u8 = undefined;
const held = std.debug.lockStderr(&buf);
defer std.debug.unlockStderr();
try held.file_writer.print("msg\n", .{});

Critical: std.crypto.random Removed (0.16)

// WRONG (0.16) — removed
std.crypto.random.bytes(&nonce);

// CORRECT — platform-specific
extern "c" fn arc4random_buf(buf: *anyopaque, nbytes: usize) void;
arc4random_buf(&nonce, nonce.len);  // macOS + Linux glibc 2.36+

// Linux-only (no glibc dependency):
_ = std.os.linux.getrandom(buf.ptr, buf.len, 0);

Note: std.posix.getrandom does NOT exist in 0.16.

Critical: Scoping Rule Tightened (0.16)

Local constants cannot shadow module-level extern declarations:

extern "c" fn socket(...) c_int;

fn myConnect() !void {
    // WRONG — "local constant shadows declaration of socket"
    const socket = blk: { ... };

    // CORRECT — use different name
    const sock_fd = blk: { ... };
}

Critical: I/O API Rewrite ("Writergate")

The entire std.io API changed. New std.Io.Writer and std.Io.Reader are non-generic with buffer in the interface.

Writing

// WRONG - old API
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello\n", .{});

// CORRECT - new API: provide buffer, access .interface, flush
var buf: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buf);
const stdout = &stdout_writer.interface;
try stdout.print("Hello\n", .{});
try stdout.flush();  // REQUIRED!

Reading

// Reading from file
var buf: [4096]u8 = undefined;
var file_reader = file.reader(&buf);
const r = &file_reader.interface;

// Read line by line (takeDelimiter returns null at EOF)
while (try r.takeDelimiter('\n')) |line| {
    // process line (doesn't include '\n')
}

// Read binary data
const header = try r.takeStruct(Header, .little);
const value = try r.takeInt(u32, .big);

Fixed Buffer Writer (no file)

var buf: [256]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
try w.print("Hello {s}", .{"world"});
const result = w.buffered();  // "Hello world"

Fixed Reader (from slice)

var r: std.Io.Reader = .fixed("hello\nworld");
const line = (try r.takeDelimiter('\n')).?;  // "hello" (returns null at EOF)

Removed: BufferedWriter, CountingWriter, std.io.bufferedWriter()

Deprecated: GenericWriter, GenericReader, AnyWriter, AnyReader, FixedBufferStream

New: std.Io.Writer, std.Io.Reader - non-generic, buffer in interface

Replacements:

  • CountingWriter -> std.Io.Writer.Discarding (has .fullCount())
  • BufferedWriter -> buffer provided to .writer(&buf) call
  • Allocating output -> std.Io.Writer.Allocating

std.io.fixedBufferStream Removed (0.16)

// WRONG (0.16) — std.io (lowercase) removed entirely
var buf: [512]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try std.fmt.format(stream.writer(), "{d}", .{value});
const result = stream.getWritten();

// CORRECT — use std.fmt.bufPrint directly
var buf: [512]u8 = undefined;
const result = try std.fmt.bufPrint(&buf, "{d}", .{value});

Vtable Writer Type Changed (0.16)

// WRONG — *std.io.Writer (lowercase)
fn drain(w: *std.io.Writer) error{WriteFailed}!usize { ... }

// CORRECT — *std.Io.Writer (capital)
fn drain(w: *std.Io.Writer) error{WriteFailed}!usize { ... }

Critical: Build System (0.15.x)

root_source_file is REMOVED from addExecutable/addLibrary/addTest. Use root_module:

// WRONG - removed field
b.addExecutable(.{
    .name = "app",
    .root_source_file = b.path("src/main.zig"),  // ERROR
    .target = target,
});

// CORRECT
b.addExecutable(.{
    .name = "app",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

Module imports changed:

// WRONG (old API)
exe.addModule("helper", helper_mod);

// CORRECT
exe.root_module.addImport("helper", helper_mod);

Libraries: addSharedLibraryaddLibrary with .linkage:

// WRONG - removed function
const lib = b.addSharedLibrary(.{ .name = "mylib", ... });

// CORRECT - unified addLibrary with linkage field
const lib = b.addLibrary(.{
    .name = "mylib",
    .linkage = .dynamic,  // or .static (default)
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

Adding dependency modules:

const dep = b.dependency("lib", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("lib", dep.module("lib"));

Compile.* Methods Moved to Module.* (0.16)

In 0.16, methods like addIncludePath, addLibraryPath, linkSystemLibrary, addCSourceFile moved from the Compile step to the module:

// WRONG (0.16) — methods no longer on Compile
lib.addIncludePath(.{ .cwd_relative = path });
lib.linkSystemLibrary("foo");
lib.addCSourceFile(.{ .file = b.path("shim.c"), .flags = &.{} });

// CORRECT — use root_module
lib.root_module.addIncludePath(.{ .cwd_relative = path });
lib.root_module.linkSystemLibrary("foo", .{});  // note: now takes options struct
lib.root_module.addCSourceFile(.{ .file = b.path("shim.c"), .flags = &.{} });

Common misleading error: no field or member function named 'addIncludePath' in 'Build.Step.Compile'. The note about .* is wrong — the fix is lib.root_module.addIncludePath(...).

See std.Build reference for complete build system documentation.

Critical: Container Initialization

Never use .{} for containers. Use .empty or .init:

// WRONG - deprecated
var list: std.ArrayList(u32) = .{};
var gpa: std.heap.DebugAllocator(.{}) = .{};

// CORRECT - use .empty for empty collections
var list: std.ArrayList(u32) = .empty;
var map: std.AutoHashMapUnmanaged(u32, u32) = .empty;

// CORRECT - use .init for stateful types with internal config
var gpa: std.heap.DebugAllocator(.{}) = .init;
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);

ArrayList: Unmanaged by Default

The old std.ArrayList (with allocator stored in struct) is now std.array_list.Managed. The new std.ArrayList is unmanaged — no allocator field, pass allocator to every method:

// NEW default: unmanaged (no allocator field)
var list: std.ArrayList(u32) = .empty;
try list.append(allocator, 42);
list.deinit(allocator);

// If you want old behavior (allocator in struct), use Managed:
var list = std.array_list.Managed(u32).init(allocator);
try list.append(42);  // no allocator arg needed
list.deinit();

HashMap: Managed vs Unmanaged

Same pattern applies — unmanaged variants require allocator per operation:

// Unmanaged (no internal allocator)
var map: std.StringHashMapUnmanaged(u32) = .empty;
try map.put(allocator, "key", 42);
map.deinit(allocator);

// Managed (stores allocator internally)
var map = std.StringHashMap(u32).init(allocator);
try map.put("key", 42);
map.deinit();

ArrayListUnmanaged Empty Init (0.16)

In 0.16, .{} zero-init no longer works for ArrayListUnmanaged — explicit fields required:

// WRONG (0.16) — .{} no longer zero-inits correctly
._list = .{},

// CORRECT — explicit fields
._list = .{ .items = &.{}, .capacity = 0 },

Naming Changes

  • std.ArrayListUnmanaged -> std.ArrayList (Unmanaged is now default, old name deprecated)
  • std.heap.GeneralPurposeAllocator -> std.heap.DebugAllocator (GPA alias still works)

Linked Lists: Generic Parameter Removed

// WRONG - old API
const Node = std.DoublyLinkedList(MyData).Node;

// CORRECT - non-generic, use @fieldParentPtr
const MyNode = struct {
    node: std.DoublyLinkedList.Node,
    data: MyData,
};
// Access: const my_node = @fieldParentPtr("node", node_ptr);

Process API: Term is Tagged Union

// WRONG - direct field access removed
if (result.term.Exited != 0) {}

// CORRECT - pattern match
switch (result.term) {
    .exited => |code| if (code != 0) { /* handle */ },
    else => {},
}

Critical: Format Strings (0.15.x)

{f} required to call format methods:

// WRONG - ambiguous error
std.debug.print("{}", .{std.zig.fmtId("x")});

// CORRECT
std.debug.print("{f}", .{std.zig.fmtId("x")});

Format method signature changed:

// OLD - wrong
pub fn format(self: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void

// NEW - correct
pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void

Breaking Changes (0.14.0+)

@branchHint replaces @setCold

// WRONG
@setCold(true);

// CORRECT
@branchHint(.cold);  // Must be first statement in block

@export takes pointer

// WRONG
@export(foo, .{ .name = "bar" });

// CORRECT
@export(&foo, .{ .name = "bar" });

Inline asm clobbers are typed

// WRONG
: "rcx", "r11"

// CORRECT
: .{ .rcx = true, .r11 = true }

@fence - REMOVED

Use stronger atomic orderings or RMW operations instead.

@typeInfo fields now lowercase

// WRONG - old PascalCase (compile error)
if (@typeInfo(T) == .Struct) { ... }
if (@typeInfo(T) == .Slice) { ... }
if (@typeInfo(T) == .Int) { ... }

// CORRECT - lowercase, keywords escaped with @""
if (@typeInfo(T) == .@"struct") { ... }
if (@typeInfo(T) == .@"enum") { ... }
if (@typeInfo(T) == .@"union") { ... }
if (@typeInfo(T) == .@"opaque") { ... }
if (@typeInfo(T) == .slice) { ... }
if (@typeInfo(T) == .int) { ... }
if (@typeInfo(T) == .bool) { ... }
if (@typeInfo(T) == .pointer) { ... }

// Accessing fields:
const fields = @typeInfo(T).@"struct".fields;
const tag_type = @typeInfo(T).@"enum".tag_type;

Decl Literals (0.14.0+)

.identifier syntax works for declarations:

const S = struct {
    x: u32,
    const default: S = .{ .x = 0 };
    fn init(v: u32) S { return .{ .x = v }; }
};

const a: S = .default;      // S.default
const b: S = .init(42);     // S.init(42)
const c: S = try .init(1);  // works with try

Labeled Switch (0.14.0+)

State machines use continue :label:

state: switch (initial) {
    .idle => continue :state .running,
    .running => if (done) break :state result else continue :state .running,
    .error => return error.Failed,
}

Non-exhaustive Enum Switch (0.15.x)

Can mix explicit tags with _ and else:

switch (value) {
    .a, .b => {},
    else => {},  // other named tags
    _ => {},     // unnamed integer values
}

Critical: HTTP API Reworked (0.15.x)

HTTP client/server completely restructured — depends only on I/O streams, not networking:

// Server now takes Reader/Writer interfaces, not connection directly
var recv_buffer: [4000]u8 = undefined;
var send_buffer: [4000]u8 = undefined;
var conn_reader = connection.stream.reader(&recv_buffer);
var conn_writer = connection.stream.writer(&send_buffer);
var server = std.http.Server.init(
    conn_reader.interface(),
    &conn_writer.interface,
);

Note: HTTP client API is still rapidly evolving. For stability-critical code, consider shelling out to curl.

Quick Fixes

Error Fix
no field 'root_source_file' Use root_module = b.createModule(.{...})
'std.net' has no member 'Stream' Networking moved: use std.Io.net.Stream (0.16)
'std.net' has no member 'Address' Use std.Io.net.IpAddress.parse(host, port) (0.16)
no field 'addIncludePath' in 'Compile' Methods moved: lib.root_module.addIncludePath(...) (0.16)
'timestamp' not found in 'std.time' Removed: use std.c.clock_gettime(.REALTIME, &ts) (0.16)
'Mutex' not found in 'std.Thread' Removed: use POSIX PthreadMutex shim or std.Io.Mutex (0.16)
'random' not found in 'std.crypto' Removed: use arc4random_buf or std.os.linux.getrandom (0.16)
'lockStderrWriter' not found Renamed: use std.debug.lockStderr(&buf) (0.16)
local constant shadows declaration 0.16 forbids local names matching module-level extern fn — rename local
signed integer division Use @divTrunc(a, b) not a / b for signed integers (0.16)
no field 'close' in 'posix' std.posix.close removed: use _ = std.c.close(fd) (0.16)
use of undefined value Arithmetic on undefined is now illegal — initialize explicitly
type 'f32' cannot represent integer Use float literal: 123_456_789.0 not 123_456_789
ambiguous format string Use {f} for format methods
no field 'append' on ArrayList Pass allocator: list.append(allocator, val) (unmanaged default)
expected 2 arguments, found 1 on ArrayList Add allocator param: .append(allocator, val), .deinit(allocator)
BoundedArray not found Use std.ArrayList(T).initBuffer(&buf)
GenericWriter/GenericReader Use std.Io.Writer/std.Io.Reader
missing .flush() — no output Always call try writer.flush() after writing
enum has no member named 'Struct' @typeInfo fields now lowercase: .@"struct", .slice, .int
no field named 'encode' on base64 Use std.base64.standard.Encoder.encode()
no field named 'open' on HTTP Use client.request() or client.fetch()
expected error union, found Signature Ed25519.Signature.fromBytes() doesn't return error — remove try
addSharedLibrary not found Use b.addLibrary(.{ .linkage = .dynamic, ... })

Verification Workflow

After writing or modifying Zig code, verify with this sequence:

  1. zig build — catch compilation errors, match against Quick Fixes above
  2. zig build test — run unit tests
  3. zig build -Doptimize=ReleaseFast test — detect undefined behavior (UB checks enabled in optimized builds)

Development speed tips:

  • zig build --watch -fincremental — incremental compilation, rebuilds on file change
  • 0.15.x uses self-hosted x86_64 backend by default — ~5x faster Debug builds than LLVM

Common Pitfalls

  • Forgetting defer/errdefer cleanup — place cleanup immediately after resource acquisition
  • Using anyerror instead of specific error sets — explicit sets document failure modes
  • Ignoring error unions — handle or propagate, never discard
  • Missing errdefer after allocations in multi-step init — partial construction leaks
  • Expecting comptime side effects — comptime code is evaluated lazily
  • Unhandled integer overflow — Zig traps on overflow in debug builds
  • Missing null terminators for C strings — use :0 sentinel slices: [:0]const u8
  • Using anytype when comptime T: type works — explicit types produce clearer errors
  • Scoped loggers: always define per-module const log = std.log.scoped(.my_module); for filterable logging

Learning Resources

Production Zig codebases worth studying:

  • Bun — JS runtime (~200k+ lines), async I/O, FFI, system calls
  • Ghostty — Terminal emulator, cross-platform, GPU rendering
  • TigerBeetle — Financial DB, deterministic execution, VOPR fuzzing
  • Mach Engine — Game engine, graphics, ECS
  • Sig — Solana validator, high-performance networking

Language References

Load these references when working with core language features:

Code Style

  • Style Guide - Official Zig naming conventions (TitleCase types, camelCase functions, snake_case variables), whitespace rules, doc comment guidance, redundancy avoidance, zig fmt

Language Basics & Built-ins

  • Language Basics - Core language: types, control flow (if/while/for/switch), error handling (try/catch/errdefer), optionals, structs, enums, unions, pointers, slices, comptime, functions
  • Built-in Functions - All @ built-ins: type casts (@intCast, @bitCast, @ptrCast), arithmetic (@addWithOverflow, @divExact), bit ops (@clz, @popCount), memory (@memcpy, @sizeOf), atomics (@atomicRmw, @cmpxchgWeak), introspection (@typeInfo, @TypeOf, @hasDecl), SIMD (@Vector, @splat, @reduce), C interop (@cImport, @export)

Standard Library References

Load these references when working with specific modules:

Memory & Slices

  • std.mem - Slice search/compare, split/tokenize, alignment, endianness, byte conversion

Text & Encoding

  • std.fmt - Format strings, integer/float parsing, hex encoding, custom formatters, {f} specifier (0.15.x)
  • std.ascii - ASCII character classification (isAlpha, isDigit), case conversion, case-insensitive comparison
  • std.unicode - UTF-8/UTF-16 encoding/decoding, codepoint iteration, validation, WTF-8 for Windows
  • std.base64 - Base64 encoding/decoding (standard, URL-safe, with/without padding)

Math & Random

  • std.math - Floating-point ops, trig, overflow-checked arithmetic, constants, complex numbers, big integers
  • std.Random - PRNGs (Xoshiro256, Pcg), CSPRNGs (ChaCha), random integers/floats/booleans, shuffle, distributions
  • std.hash - Non-cryptographic hash functions (Wyhash, XxHash, FNV, Murmur, CityHash), checksums (CRC32, Adler32), auto-hashing

SIMD & Vectorization

  • std.simd - SIMD vector utilities: optimal vector length, iota/repeat/join/interlace patterns, element shifting/rotation, parallel searching, prefix scans, branchless selection

Time & Timing

  • std.time - Wall-clock timestamps, monotonic Instant/Timer, epoch conversions, calendar utilities (year/month/day), time unit constants
  • std.Tz - TZif timezone database parsing (RFC 8536), UTC offsets, DST rules, timezone abbreviations, leap seconds

Sorting & Searching

  • std.sort - Sorting algorithms (pdq, block, heap, insertion), binary search, min/max

Core Data Structures

Allocators

  • std.heap - Allocator selection guide, ArenaAllocator, DebugAllocator, FixedBufferAllocator, MemoryPool, SmpAllocator, ThreadSafeAllocator, StackFallbackAllocator, custom allocator implementation

I/O & Files

  • std.io - Reader/Writer API (0.15.x): buffered I/O, streaming, binary data, format strings
  • std.fs - File system: files, directories, iteration, atomic writes, paths
  • std.tar - Tar archive reading/writing, extraction, POSIX ustar, GNU/pax extensions
  • std.zip - ZIP archive reading/extraction, ZIP64 support, store/deflate compression
  • std.compress - Compression: DEFLATE (gzip, zlib), Zstandard, LZMA, LZMA2, XZ decompression/compression

Networking

  • std.http - HTTP client/server, TLS, connection pooling, compression, WebSocket
  • std.net - TCP/UDP sockets, address parsing, DNS resolution
  • std.Uri - URI parsing/formatting (RFC 3986), percent-encoding/decoding, relative URI resolution

Process Management

  • std.process - Child process spawning, environment variables, argument parsing, exec

OS-Specific APIs

  • std.os - OS-specific APIs: Linux syscalls, io_uring, Windows NT APIs, WASI, direct platform access
  • std.c - C ABI types and libc bindings: platform-specific types (fd_t, pid_t, timespec), errno values, socket/signal/memory types, fcntl/open flags, FFI with C libraries

Concurrency

  • std.Thread - Thread spawning, Mutex, RwLock, Condition, Semaphore, WaitGroup, thread pools
  • std.atomic - Lock-free atomic operations: Value wrapper, fetch-and-modify (add/sub/and/or/xor), compare-and-swap, atomic ordering semantics, spin loop hints, cache line sizing

Patterns & Best Practices

  • Zig Patterns - Load when writing new code or reviewing code quality. Comprehensive best practices extracted from the Zig standard library: quick patterns (memory/allocators, file I/O, HTTP, JSON, testing, build system) plus idiomatic code patterns covering syntax (closures, context pattern, options structs, destructuring), polymorphism (duck typing, generics, custom formatting, dynamic/static dispatch), safety (diagnostics, error payloads, defer/errdefer, compile-time assertions), and performance (const pointer passing)
  • Production Patterns - Load when building large-scale Zig systems or optimizing performance. Real-world patterns from Bun, Ghostty, TigerBeetle: modular build systems, CPU feature locking, pre-allocated message pools, counting allocators, SIMD with scalar fallback, intrusive linked lists, cache-line aligned SoA, work-stealing thread pools, SmolStr (15-byte SSO), comptime string maps, EnumUnionType generation, VOPR fuzzing, snapshot testing, edge-biased fuzz generation, platform abstraction facades, Objective-C bridges, opaque C wrappers with RAII, packed struct bitfields, Result union types, radix sort, tournament trees
  • MCP Server Patterns - Load when building MCP servers, LSP bridges, JSON-RPC services, or protocol translators in Zig. Patterns from zig-mcp: newline-delimited vs Content-Length transport, thread-based request correlation with ResetEvent, arena-per-request memory, child process lifecycle with pipe ownership transfer, tool registry with function pointers, std.json.Stringify for manual JSON building, lazy document sync with double-check locking, graceful degradation, auto-reconnect on crash, comptime schema generation, file URI encoding, common serialization gotchas
  • Code Review - Load when reviewing Zig code. Systematic checklist organized by confidence level: ALWAYS FLAG (removed features, changed syntax, API changes), FLAG WITH CONTEXT (exception safety bugs, missing flush, allocator issues), SUGGEST (style improvements). Includes migration examples for 0.14/0.15 breaking changes

Serialization

  • std.json - JSON parsing, serialization, dynamic values, streaming, custom parse/stringify
  • std.zon - ZON (Zig Object Notation) parsing and serialization for build.zig.zon, config files, data interchange

Testing & Debug

  • std.testing - Unit test assertions and utilities
  • std.debug - Panic, assert, stack traces, hex dump, format specifiers
  • std.log - Scoped logging with configurable levels and output

Metaprogramming

  • Comptime Reference - Comptime fundamentals, type reflection (@typeInfo/@Type/@TypeOf), loop variants (comptime for vs inline for), branch elimination, type generation, comptime limitations
  • std.meta - Type introspection, field iteration, stringToEnum, generic programming

Compiler Utilities

  • std.zig - AST parsing, tokenization, source analysis, linters, formatters, ZON parsing

Security & Cryptography

  • std.crypto - Hashing (SHA2, SHA3, Blake3), AEAD (AES-GCM, ChaCha20-Poly1305), signatures (Ed25519, ECDSA), key exchange (X25519), password hashing (Argon2, scrypt, bcrypt), secure random, timing-safe operations

Build System

  • std.Build - Build system: build.zig, modules, dependencies, build.zig.zon, steps, options, testing, C/C++ integration

Interoperability

  • C Interop - Exporting C-compatible APIs: export fn, C calling convention, building static/dynamic libraries, creating headers, macOS universal binaries, XCFramework for Swift/Xcode, module maps

Tooling

ZLS (Zig Language Server)

IDE support via Language Server Protocol. Provides autocomplete, go-to-definition, hover docs, diagnostics.

Version matching rule: Use ZLS release matching your Zig release (0.15.x ZLS for 0.15.x Zig). Nightly Zig needs nightly ZLS.

Installation:

# VS Code: install "Zig Language" extension (includes ZLS)

# Manual / other editors:
# Download from https://github.com/zigtools/zls/releases
# Or build from source:
git clone https://github.com/zigtools/zls
cd zls && git checkout 0.15.0  # match your Zig version
zig build -Doptimize=ReleaseSafe

# Configure:
zls --config

Editor support: VS Code, Neovim (nvim-lspconfig), Helix, JetBrains, Emacs (lsp-mode), Sublime Text, Kate.

Key features:

  • Autocomplete with semantic analysis
  • Go-to-definition, find references
  • Hover documentation
  • Diagnostics (compile errors inline)
  • Filesystem completions inside @import("") strings
  • std and builtin module path completions
  • Snippets for common declarations

zigup (Version Manager)

Manage multiple Zig versions side-by-side. Useful for migrating between versions.

Source: https://github.com/marler8997/zigup

Installation:

brew install zigup

Usage:

zigup fetch 0.15.2          # download without changing default
zigup fetch 0.16.0          # download without changing default
zigup list                  # show installed versions
zigup 0.16.0                # set as default
zigup run 0.15.2 build      # run specific version without changing default
zigup run 0.16.0 build test # test with specific version
zigup clean 0.15.2          # remove old version

Migration workflow:

zigup run 0.16.0 zig build 2>&1 | head -40  # try build with new version
# fix errors, re-run until clean
zigup 0.16.0                                  # promote to default

anyzig (Version Manager)

Universal Zig version manager — run any Zig version from any project. Replaces manual version switching.

Source: https://github.com/marler8997/anyzig

How it works:

  1. Reads minimum_zig_version from build.zig.zon (searches up directory tree)
  2. Auto-downloads needed compiler version into global cache
  3. Invokes the correct zig transparently

Installation:

brew install anyzig

For other platforms, see https://github.com/marler8997/anyzig

Usage:

# Automatic — reads build.zig.zon minimum_zig_version:
cd myproject && zig build

# Manual version override:
zig 0.13.0 build-exe myproject.zig
zig 0.15.2 build

# Mach engine versions supported:
# Reads .mach_zig_version from build.zig.zon
# Format: 2024.10.0-mach

# anyzig-specific commands:
zig any --help

build.zig.zon version field:

.{
    .name = .myproject,
    .version = "0.1.0",
    .minimum_zig_version = "0.15.2",
    // ...
}
Installs
15
GitHub Stars
5
First Seen
Feb 6, 2026