agentskills.codes

Up-to-date Zig programming language patterns for version 0.16.0. 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

Install

mkdir -p .claude/skills/zig && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16532" && unzip -o skill.zip -d .claude/skills/zig && rm skill.zip

Installs to .claude/skills/zig

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Up-to-date Zig programming language patterns for version 0.16.0. 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).
777 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

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 -> `

Content truncated.

Search skills

Search the agent skills registry