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