本文へ移動
cccskills
無料GitHub で公開

zig

Up-to-date Zig programming language patterns for version 0.17.0-dev (0.16.0 stable APIs plus the 0.17 deltas). Use when writing, reviewing, or debugging Zig code, working with build.zig and build.zig.zon files, or using comptime metaprogramming. Critical for avoiding outdated patterns from training data - especially std.net→std.Io.net (requires Io instance), std.time timestamps removed (use clock_gettime), std.Thread.Mutex/Condition/sleep removed (use pthreads), std.crypto.random removed, build system APIs (root_module, Compile methods→Module methods), I/O APIs (buffered writer pattern), container initialization (.empty/.init), allocator selection (DebugAllocator), ArrayList now unmanaged by default, @typeInfo lowercase fields (.@"struct" not .Struct), and removed language features (async/await, usingnamespace). Also covers 0.17.0-dev deltas: b.args→run_cmd.addPassthruArgs(), std.gpu→std.spirv, @bitCast logical-bit (endian-agnostic) semantics, build configurer/maker split, and the zig-pkg/ package directory. Also covers quality tooling Zig does not ship: coverage with kcov (and why in-file tests wreck the denominator), detecting unused private functions (neither the compiler nor zlint's unused-decls finds them), duplicate detection with jscpd, zlint (NOT the same-named X.509 linter in Homebrew), and the built-in fuzzer whose testOne now takes *std.testing.Smith rather than []const u8.

インストール方法を見る

含まれるファイル(61)

  • SKILL.md48.7 KB
  • references/builtins.md16.5 KB
  • references/c-interop.md19.3 KB
  • references/code-review.md45.1 KB
  • references/comptime.md11.0 KB
  • references/data-oriented-design.md8.8 KB
  • references/language.md13.7 KB
  • references/mcp-server-patterns.md22.2 KB
  • references/patterns.md44.6 KB
  • references/production-patterns.md25.8 KB
  • references/quality-tooling.md8.8 KB
  • references/simd-intrinsics.md6.3 KB
  • references/std-allocators.md16.1 KB
  • references/std-array-hash-map.md4.8 KB
  • references/std-arraylist.md4.4 KB
  • references/std-ascii.md3.5 KB
  • references/std-atomic.md11.8 KB
  • references/std-base64.md3.8 KB
  • references/std-bit-set.md4.5 KB
  • references/std-buf-map.md3.9 KB
  • references/std-build.md29.0 KB
  • references/std-c.md25.4 KB
  • references/std-compress.md10.2 KB
  • references/std-crypto.md14.6 KB
  • references/std-debug.md11.4 KB
  • references/std-enums.md6.4 KB
  • references/std-fmt.md13.1 KB
  • references/std-fs.md10.8 KB
  • references/std-hash.md11.7 KB
  • references/std-hashmap.md3.6 KB
  • references/std-http.md17.1 KB
  • references/std-io.md15.6 KB
  • references/std-json.md9.9 KB
  • references/std-linked-list.md3.6 KB
  • references/std-log.md5.9 KB
  • references/std-math.md12.1 KB
  • references/std-mem.md7.7 KB
  • references/std-meta.md10.8 KB
  • references/std-multi-array-list.md3.2 KB
  • references/std-net.md21.0 KB
  • references/std-os.md14.1 KB
  • references/std-priority-dequeue.md3.7 KB
  • references/std-priority-queue.md3.9 KB
  • references/std-process.md13.4 KB
  • references/std-random.md9.9 KB
  • references/std-segmented-list.md2.9 KB
  • references/std-simd.md17.0 KB
  • references/std-sort.md6.0 KB
  • references/std-static-string-map.md4.6 KB
  • references/std-tar.md12.3 KB
  • references/std-testing.md10.8 KB
  • references/std-thread.md15.0 KB
  • references/std-time.md11.1 KB
  • references/std-treap.md3.4 KB
  • references/std-tz.md6.0 KB
  • references/std-unicode.md7.2 KB
  • references/std-uri.md12.3 KB
  • references/std-zig.md17.6 KB
  • references/std-zip.md9.0 KB
  • references/std-zon.md11.0 KB
  • references/style-guide.md5.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Zig Language Reference (v0.17.0-dev)

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

Version coverage: 0.17.0-dev (current master) — every 0.16.0 stable pattern below still holds, with migration notes from 0.15.x and 0.14.x and the 0.17 deltas in the section directly below.

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: 0.17.0-dev changes (in progress)

Status: 0.17.0 is unreleased as of mid-2026 — master/-dev only, no official release notes yet (0.17.0/release-notes.html 404s). The only migration source is the devlog. Every 0.16.0 pattern in the rest of this skill still applies — the items below are the additional deltas, verified against 0.17.0-dev.1158. Pin a dev build in build.zig.zon (.minimum_zig_version = "0.17.0-dev.NNN+hash"); anyzig fetches it.

Unchanged from 0.16 (verified present in 0.17-dev.956): std.Io.net, std.Io.Threaded, std.ArrayList (unmanaged default), std.debug.lockStderr, and the time/thread/crypto shims — all 0.16 sections below still hold.

b.args REMOVED → run_cmd.addPassthruArgs()

The one change nearly every project needs. build.zig no longer observes CLI args (they now bypass build-script recompilation):

// WRONG (0.17) — error: no field named 'args' in struct 'Build'
if (b.args) |args| run_cmd.addArgs(args);

// CORRECT (0.17)
run_cmd.addPassthruArgs();   // forwards `zig build run -- a b c` to the spawned process

Build system reworked: configurer / maker split

build.zig is now compiled in debug mode into a "configurer" that serializes the build graph to a binary file; a separately-cached "maker" executes it in release mode. Net effect: zig build invocation ~90% faster (zig build --help ~150ms → ~14ms) and changing build args no longer rebuilds build.zig. Mostly transparent — but it's why b.args had to go.

std.gpu → std.spirv

GPU/shader namespace renamed (std/gpu.zig is gone, std/spirv.zig replaces it). std.gpu.executionMode() removed — execution modes now ride on the calling convention, and @SpirvType (new builtin) expresses samplers/images/runtime-arrays:

// SPIR-V entry points carry execution mode in the calling-convention payload (0.17)
export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
// also: .spirv_vertex / .spirv_fragment / .spirv_task / .spirv_mesh

@bitCast semantics redesign — logical bit layout (proposal #19755)

@bitCast now reinterprets a type's logical bits, not its in-memory bytes — so it is endian-agnostic (aggregates behave as little-endian on every target). Newly enables casts like [2]u3 → @Vector(3, u2); now allowed on enums; now disallowed on vectors-of-pointers. Code that relied on big-endian in-memory @bitCast of arrays/structs changes behavior.

Package layout: zig-pkg/ + --fork

Fetched dependencies now land in a visible zig-pkg/ at project root (previously hidden in the global cache) — add it to .gitignore. New zig build --fork=<path> temporarily overrides a dependency without editing build.zig.zon.

std.Io.Evented (experimental)

Event-driven Io backends added — io_uring (Linux) and Grand Central Dispatch (macOS) — alongside std.Io.Threaded. Still experimental.

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.net → std.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, Timer — still 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.spawn — unchanged.

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: addSharedLibrary → addLibrary 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

ErrorFix
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 foundRenamed: use std.debug.lockStderr(&buf) (0.16)
local constant shadows declaration0.16 forbids local names matching module-level extern fn — rename local
signed integer divisionUse @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 valueArithmetic on undefined is now illegal — initialize explicitly
type 'f32' cannot represent integerUse float literal: 123_456_789.0 not 123_456_789
ambiguous format stringUse {f} for format methods
no field 'append' on ArrayListPass allocator: list.append(allocator, val) (unmanaged default)
expected 2 arguments, found 1 on ArrayListAdd allocator param: .append(allocator, val), .deinit(allocator)
BoundedArray not foundUse std.ArrayList(T).initBuffer(&buf)
GenericWriter/GenericReaderUse std.Io.Writer/std.Io.Reader
missing .flush() — no outputAlways call try writer.flush() after writing
enum has no member named 'Struct'@typeInfo fields now lowercase: .@"struct", .slice, .int
no field named 'encode' on base64Use std.base64.standard.Encoder.encode()
no field named 'open' on HTTPUse client.request() or client.fetch()
expected error union, found SignatureEd25519.Signature.fromBytes() doesn't return error — remove try
addSharedLibrary not foundUse b.addLibrary(.{ .linkage = .dynamic, ... })
no field named 'args' in struct 'Build'Removed in 0.17: run_cmd.addPassthruArgs() instead of `if (b.args)
'std' has no member named 'gpu'Renamed in 0.17: std.spirv; executionMode() → calling-convention payload
ZLS build: Zig version ... is not yet supportedZLS pins an exact dev Zig — build with the README-pinned version: zig <pinned> build

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
  • Hardware SIMD intrinsics - Beyond @Vector: calling LLVM target intrinsics (extern fn @"llvm.x86.avx2.pmadd.wd" / @"llvm.aarch64.neon.udot…") for ops with no @Vector form (udot, vpsadbw, vpmaddubsw); why this beats inline asm (optimizer-transparent vs opaque); perf gotchas (natural result layout, comptime mode params, manual unroll); cross-arch validation via Rosetta / static-musl + scp / asm-diff

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

  • Data-Oriented Design - Load when designing data structures for hot paths, large homogeneous collections, compilers/parsers, ECS, or any memory-footprint-bound code. From Andrew Kelly's DoD talk applied to the Zig compiler: cache-line mental model (CPU fast, memory slow; compute over memoize), struct size/alignment/padding rules, and six shrink-the-struct techniques — indexes instead of pointers (with enum(u32) newtype handles), booleans out of band, struct-of-arrays via MultiArrayList, sparse data in hash maps, encodings instead of fat tagged unions, and constraining ranges/dropping derivable data. Includes the compiler case study (token 64→5 B, AST 120→15.6 B, ZIR 54→20.3 B; −22% then −39% wall-clock) and an anti-pattern checklist
  • 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

Quality & Analysis

  • Quality Tooling - Coverage (kcov, and why in-file tests wreck the denominator), dead-code detection (nothing finds unused functions — compiler laziness, zlint's unused-decls covers only constants), duplicate detection (jscpd + the --max-lines trap), zlint (⚠️ Homebrew's zlint is an X.509 linter), and the built-in fuzzer (*Smith signature, corpus economics, coverage plateaus)

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

Quality checks (coverage, dead code, duplication, lint, fuzz)

Zig ships none of these. Full details and measurements: Quality Tooling. The traps that silently produce a green result:

  • Tests share the file with the code (the only way to reach private decls), so coverage and lint reports must exclude a line range, not a file. Measured: 99.7% over 1325 lines with tests counted vs 99.5% over 382 without — a 3.5× difference in denominator.
  • kcov works on Zig binaries with no instrumentation, macOS included. Use codecov.json for per-line data ("taken/total" strings); coverage.json is summary-only.
  • Nothing detects an unused private function. The compiler never analyses it, and zlint's unused-decls covers constants/variables only — resolving a call through anytype would mean monomorphising. Rename the definition and rebuild to prove it; a syntactic "identifier occurs once" prefilter is instant and can only err by missing.
  • jscpd --max-lines defaults to 1000 and skips longer files silently. Pass --format c --formats-exts "c:zig" --max-lines 100000.
  • ⚠️ zlint in Homebrew is zmap/zlint, an X.509 certificate linter. The Zig one is DonIsaac/zlint, release binary only. Its config replaces the rule set rather than extending it.
  • Fuzzer: testOne takes *std.testing.Smith, not []const u8 (see std.testing). The persisted corpus is what makes coverage guidance pay off; from an empty corpus it is no better than random. Two concurrent runs abort and leave a bogus crash artifact.

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.

Nightly/-dev gotcha (learned the hard way): ZLS master pins an exact Zig dev build (see minimum_zig_version in its build.zig.zon and the "default branch targets 0.17.0-dev.NNN" line in its README), and build.zig hard-errors on Zig newer than its supported window (The used Zig version ... is not yet supported by ZLS). Two traps:

  • ZLS master often lags the latest Zig master by weeks, so the newest downloadable Zig (zig version) may be rejected by ZLS.
  • ziglang.org's CDN keeps only the latest master build plus tagged releases — older -dev builds get pruned and 404 on download. The exact version ZLS pins may or may not still be downloadable.

Build ZLS against its README-pinned Zig explicitly (anyzig will fetch it if still available):

cd zls
git pull                                      # ZLS master targets newest supported Zig
grep -n "default branch.*targets\|0\.1" README.md   # find the pinned 0.NN.0-dev.NNN+hash
zig <pinned-version> build -Doptimize=ReleaseSafe   # e.g. zig 0.17.0-dev.387+31f157d80 build

If the pinned build 404s, either use a Zig version inside ZLS's supported range (build.zig rejects >= max), or wait for ZLS to bump its pin to a currently-downloadable Zig.

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",
    // ...
}

Profiling (macOS / Apple Silicon)

Two complementary angles: deterministic allocation counts (no sampling noise, immune to inlining) and CPU sampling (where wall-time goes).

Allocation profiling — wrap the allocator, count calls. The single best signal for "is this parser/codec alloc-bound?". A counting Allocator vtable wrapper over a child allocator tallies alloc/resize/remap/free + bytes + peak-live. Run one parse, print the tally. Deterministic and reproducible — e.g. it instantly reveals a "dupe-per-string" parser doing ~1 malloc per token (thousands of allocs for a small file) vs an arena parser doing a handful. Caveat: an arena-based parser hides logical allocations behind a few big chunk mallocs — to count logical requests, wrap the arena's allocator, not its backing allocator.

const Counting = struct {
    child: std.mem.Allocator,
    allocs: usize = 0, bytes: usize = 0, live: usize = 0, peak: usize = 0,
    pub fn allocator(self: *Counting) std.mem.Allocator {
        return .{ .ptr = self, .vtable = &.{ .alloc = a, .resize = r, .remap = rm, .free = f } };
    }
    fn a(ctx: *anyopaque, len: usize, al: std.mem.Alignment, ra: usize) ?[*]u8 {
        const s: *Counting = @ptrCast(@alignCast(ctx));
        const p = s.child.rawAlloc(len, al, ra) orelse return null;
        s.allocs += 1; s.bytes += len; s.live += len; s.peak = @max(s.peak, s.live);
        return p;
    }
    // resize/remap update live; free does live -= buf.len; all delegate to s.child.raw*()
};

CPU sampling — Instruments via xctrace (works; needs dSYM). instruments GUI launcher is gone in recent Xcode; use the xctrace CLI. The critical step is dsymutil: with a dSYM, xctrace export symbolicates even inlined frames, so a ReleaseFast recursive-descent parser (everything inlined into main) still shows per-function attribution. Without it you get a flat main+0xNNNN blob — which is also why bare /usr/bin/sample is nearly useless on optimized Zig.

zig build                                   # do NOT strip; keep debug info
dsymutil ./zig-out/bin/bench                # -> bench.dSYM (symbolicates inlined frames)
xctrace record --template 'Time Profiler' --time-limit 5s \
  --output prof.trace --launch -- ./zig-out/bin/bench <args>   # or: --attach <pid>
open prof.trace                             # GUI: call tree / flame graph / source
# CLI export (XML only, slow):
xctrace export --input prof.trace \
  --xpath '/trace-toc/run[@number="1"]/data/table[@schema="time-profile"]' --output tp.xml
# then aggregate symbol names out of the backtraces (grep/sort/uniq)

Gotchas:

  • ReleaseFast inlines aggressively → leaf attribution merges. dSYM symbolication of inline frames usually recovers it; otherwise profile ReleaseSafe (less inlining, still optimized) or mark hot fns noinline temporarily.
  • xctrace export is XML-only and slow; for interactive use prefer open prof.trace.
  • Apple Silicon historically limited kernel callstack sampling, but user-space Time Profiler works fine.

Alternatives (often nicer on Apple Silicon):

  • samply record ./bin <args> — opens the Firefox Profiler UI (call tree, flamegraph, source). brew install samply / cargo install samply.
  • poop ./old ./new — A/B compare two binaries on hardware counters (instructions, branch misses, cache misses). Ideal for before/after on an optimization. Linux-native; on macOS counters are limited.

See Production Patterns (counting allocators) for the full instrumented-allocator pattern.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

このスキルの問題を報告する