AgentSkillsCN

Cro

Cro

SKILL.md

Loris Cro — Practical Zig

Write Zig code in the style of Loris Cro, VP Community at Zig Software Foundation. Emphasizes practical patterns, build system mastery, and teaching Zig effectively.

Core Philosophy

1. Build System First

The build system is part of your application. Use it to:

  • Configure compile-time options
  • Generate code
  • Manage dependencies
  • Create test fixtures

2. Practical Over Pure

Choose practical solutions that ship over theoretically perfect ones that don't.

3. Error Messages Are UX

Invest in clear error messages. They're how users interact with your tool.

Build System Patterns

Dependency Management

zig
// build.zig
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    
    // Fetch dependency
    const clap = b.dependency("clap", .{
        .target = target,
        .optimize = optimize,
    });
    
    const exe = b.addExecutable(.{
        .name = "corey",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    
    exe.root_module.addImport("clap", clap.module("clap"));
    b.installArtifact(exe);
}

build.zig.zon for Dependencies

zig
.{
    .name = "corey",
    .version = "0.1.0",
    .dependencies = .{
        .clap = .{
            .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.17.0.tar.gz",
            .hash = "...",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

Compile-Time Configuration

zig
// build.zig
const version = b.option([]const u8, "version", "Build version") orelse "dev";
const options = b.addOptions();
options.addOption([]const u8, "version", version);
exe.root_module.addOptions("build_options", options);

// src/main.zig
const build_options = @import("build_options");
const version = build_options.version;

CLI Argument Parsing

Using zig-clap

zig
const clap = @import("clap");

const params = clap.parseParams(
    \\-h, --help       Display help
    \\-v, --verbose    Enable verbose output
    \\-o, --output <FILE>  Output file
    \\<COMMAND>        Command to run
    \\
);

pub fn main() !void {
    var args = try clap.parse(params, std.process.args(), .{});
    defer args.deinit();
    
    if (args.flags.help) {
        return clap.help(std.io.getStdOut().writer(), params, .{});
    }
    
    const cmd = args.positionals[0] orelse return error.NoCommand;
    try runCommand(cmd, args);
}

Manual Parsing (Simple Cases)

zig
pub fn main() !void {
    var args = std.process.args();
    _ = args.skip(); // Skip program name
    
    const command = args.next() orelse {
        try printUsage();
        return;
    };
    
    if (std.mem.eql(u8, command, "status")) {
        try runStatus(&args);
    } else if (std.mem.eql(u8, command, "sync")) {
        try runSync(&args);
    } else {
        std.debug.print("Unknown command: {s}\n", .{command});
        return error.UnknownCommand;
    }
}

Testing Patterns

Table-Driven Tests

zig
test "parseHost" {
    const cases = .{
        .{ "github.com", .{ .host = "github.com", .port = null } },
        .{ "github.com:443", .{ .host = "github.com", .port = 443 } },
        .{ "192.168.1.1", .{ .host = "192.168.1.1", .port = null } },
    };
    
    inline for (cases) |case| {
        const input = case[0];
        const expected = case[1];
        const result = try parseHost(input);
        try std.testing.expectEqualStrings(expected.host, result.host);
        try std.testing.expectEqual(expected.port, result.port);
    }
}

Test Allocator

zig
test "credential storage" {
    // Detects leaks in tests
    const allocator = std.testing.allocator;
    
    var store = try CredentialStore.init(allocator);
    defer store.deinit();
    
    try store.set("github.com", .{ .username = "user", .token = "tok" });
    const cred = try store.get("github.com");
    try std.testing.expectEqualStrings("user", cred.username);
}

Temporary Directories

zig
test "config file loading" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    
    // Create test config
    const config_file = try tmp.dir.createFile("config.toml", .{});
    try config_file.writeAll("[defaults]\noutput = \"json\"\n");
    config_file.close();
    
    const config = try Config.load(tmp.dir, "config.toml");
    try std.testing.expectEqual(.json, config.output);
}

I/O Patterns

Buffered Writers

zig
pub fn writeCredentials(creds: []const Credential, writer: anytype) !void {
    var buffered = std.io.bufferedWriter(writer);
    const w = buffered.writer();
    
    for (creds) |cred| {
        try w.print("{s}\t{s}\t{s}\n", .{
            cred.host,
            cred.username,
            if (cred.expires_at) |e| @as(i64, e) else 0,
        });
    }
    
    try buffered.flush();
}

JSON Output

zig
const std = @import("std");

pub fn outputJson(writer: anytype, value: anytype) !void {
    try std.json.stringify(value, .{ .whitespace = .indent_2 }, writer);
    try writer.writeByte('\n');
}

// Usage
const status = Status{
    .github_cli = .{ .status = .ok, .user = "alice" },
    .git_config = .{ .status = .ok },
};
try outputJson(std.io.getStdOut().writer(), status);

Process Execution

Running External Commands

zig
fn runGitHubCLI(allocator: Allocator, args: []const []const u8) ![]u8 {
    var argv = std.ArrayList([]const u8).init(allocator);
    defer argv.deinit();
    
    try argv.append("gh");
    try argv.appendSlice(args);
    
    var child = std.ChildProcess.init(argv.items, allocator);
    child.stderr_behavior = .Pipe;
    child.stdout_behavior = .Pipe;
    
    try child.spawn();
    
    const stdout = try child.stdout.?.readToEndAlloc(allocator, 1024 * 1024);
    errdefer allocator.free(stdout);
    
    const term = try child.wait();
    if (term.Exited != 0) {
        allocator.free(stdout);
        return error.CommandFailed;
    }
    
    return stdout;
}

Checking Command Availability

zig
fn isCommandAvailable(cmd: []const u8) bool {
    var child = std.ChildProcess.init(&.{ "which", cmd }, std.heap.page_allocator);
    child.stdout_behavior = .Ignore;
    child.stderr_behavior = .Ignore;
    
    const term = child.spawnAndWait() catch return false;
    return term.Exited == 0;
}

Practical Patterns

Optional Chaining

zig
fn getConfigValue(config: ?*Config, key: []const u8) ?[]const u8 {
    const c = config orelse return null;
    const section = c.getSection("defaults") orelse return null;
    return section.get(key);
}

Sentinel-Terminated Strings

zig
// For C interop
fn callCFunction(path: [:0]const u8) void {
    c.some_c_function(path.ptr);
}

// Convert from slice
const path: []const u8 = "/path/to/file";
const path_z = try allocator.dupeZ(u8, path);
defer allocator.free(path_z);

When to Apply This Skill

  • Setting up build.zig and dependencies
  • Implementing CLI argument parsing
  • Writing tests with the testing framework
  • Doing I/O (files, processes, network)
  • Integrating with C libraries
  • Creating practical, shippable code