Andrew Kelley — Zig Philosophy
Write Zig code in the style of Andrew Kelley, creator of Zig. Emphasizes simplicity, explicit behavior, compile-time metaprogramming, and being a better C.
Core Philosophy
1. Simplicity Over Cleverness
Zig's goal is to be simple to read and write. Avoid abstractions that hide what's happening.
zig
// BAD: Clever, hard to follow
fn process(comptime T: type, items: []T) !void {
inline for (std.meta.fields(T)) |f| { ... }
}
// GOOD: Explicit, obvious
fn processCredential(cred: *Credential) !void {
try validateHost(cred.host);
try validateToken(cred.token);
}
2. No Hidden Control Flow
- •No exceptions (use error unions)
- •No hidden allocations
- •No operator overloading
- •No implicit type coercion
zig
// Errors are values, explicitly handled
const result = doSomething() catch |err| {
log.err("failed: {}", .{err});
return err;
};
3. Explicit is Better Than Implicit
zig
// BAD: What allocator? What happens on OOM? var list = ArrayList(u8).init(); // GOOD: Allocator is explicit var list = ArrayList(u8).init(allocator); defer list.deinit();
4. Compile-Time Over Runtime
Use comptime to eliminate runtime overhead:
zig
// Compile-time string formatting
const endpoint = comptime std.fmt.comptimePrint(
"https://api.github.com/users/{s}",
.{username},
);
// Compile-time type validation
fn Command(comptime name: []const u8) type {
comptime {
if (name.len == 0) @compileError("command name required");
}
return struct { ... };
}
Memory Management
Allocator Discipline
zig
pub fn main() !void {
// Use GeneralPurposeAllocator for debug builds
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
try run(allocator);
}
Ownership is Explicit
zig
// Caller owns the returned memory
fn readFile(allocator: Allocator, path: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return file.readToEndAlloc(allocator, max_size);
}
// Usage: caller must free
const data = try readFile(allocator, "config.toml");
defer allocator.free(data);
Arena Allocators for Request Scope
zig
fn handleCommand(gpa: Allocator, args: []const []const u8) !void {
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
const allocator = arena.allocator();
// All allocations freed at once when arena is deinited
const config = try loadConfig(allocator);
const result = try execute(allocator, config, args);
try output(result);
}
Error Handling
Error Sets are Types
zig
const CredentialError = error{
NotFound,
Expired,
InvalidFormat,
PermissionDenied,
NetworkError,
};
fn getCredential(host: []const u8) CredentialError!Credential {
...
}
Errors Propagate Explicitly
zig
// Use `try` to propagate
fn sync() !void {
const cred = try getCredential("github.com");
try pushToTarget(cred);
}
// Use `catch` to handle
fn syncWithFallback() !void {
const cred = getCredential("github.com") catch |err| switch (err) {
error.NotFound => try createCredential(),
else => return err,
};
try pushToTarget(cred);
}
errdefer for Cleanup on Error
zig
fn createSecureStore(allocator: Allocator) !*Store {
const store = try allocator.create(Store);
errdefer allocator.destroy(store);
store.key = try deriveKey();
errdefer zeroMemory(store.key);
store.db = try openDatabase();
// No errdefer needed - we're returning success
return store;
}
Structs and Data
Prefer Structs Over Classes
zig
const Credential = struct {
host: []const u8,
username: []const u8,
token: []const u8,
expires_at: ?i64,
pub fn isExpired(self: Credential) bool {
const now = std.time.timestamp();
return if (self.expires_at) |exp| now > exp else false;
}
pub fn format(
self: Credential,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
try writer.print("{s}@{s}", .{ self.username, self.host });
}
};
Tagged Unions for Variants
zig
const CredentialSource = union(enum) {
github_cli: struct { config_path: []const u8 },
git_credential: struct { helper: []const u8 },
keychain: struct { service: []const u8 },
environment: struct { var_name: []const u8 },
pub fn name(self: CredentialSource) []const u8 {
return @tagName(self);
}
};
Build System
build.zig is Code
zig
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "corey",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Link system libraries
exe.linkSystemLibrary("secret-1"); // libsecret for Linux keyring
exe.linkLibC();
b.installArtifact(exe);
// Test step
const tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_tests.step);
}
When to Apply This Skill
- •Designing data structures
- •Implementing error handling
- •Writing allocation-aware code
- •Creating compile-time abstractions
- •Building the project structure
- •Optimizing for readability and debuggability