zig-best-practices by 0xbigboss/claude-code
npx skills add https://github.com/0xbigboss/claude-code --skill zig-best-practices类型在实现之前定义契约。遵循以下工作流程:
使用 Zig 的类型系统在编译时防止无效状态。
用于互斥状态的标签联合体:
// 良好:只允许有效组合
const RequestState = union(enum) {
idle,
loading,
success: []const u8,
failure: anyerror,
};
fn handleState(state: RequestState) void {
switch (state) {
.idle => {},
.loading => showSpinner(),
.success => |data| render(data),
.failure => |err| showError(err),
}
}
// 不良:允许无效组合
const RequestState = struct {
loading: bool,
data: ?[]const u8,
err: ?anyerror,
};
用于失败模式的显式错误集:
// 良好:精确记录可能失败的情况
const ParseError = error{
InvalidSyntax,
UnexpectedToken,
EndOfInput,
};
fn parse(input: []const u8) ParseError!Ast {
// 实现
}
// 不良:anyerror 隐藏了失败模式
fn parse(input: []const u8) anyerror!Ast {
// 实现
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
用于领域概念的独特类型:
// 防止混淆不同类型的 ID
const UserId = enum(u64) { _ };
const OrderId = enum(u64) { _ };
fn getUser(id: UserId) !User {
// 编译器阻止在此处传递 OrderId
}
fn createUserId(raw: u64) UserId {
return @enumFromInt(raw);
}
用于不变量的编译期验证:
fn Buffer(comptime size: usize) type {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
if (size > 1024 * 1024) {
@compileError("buffer size exceeds 1MB limit");
}
return struct {
data: [size]u8 = undefined,
len: usize = 0,
};
}
用于可扩展性的非穷举枚举:
// 可能增加变体的外部枚举
const Status = enum(u8) {
active = 1,
inactive = 2,
pending = 3,
_,
};
fn processStatus(status: Status) !void {
switch (status) {
.active => {},
.inactive => {},
.pending => {},
_ => return error.UnknownStatus,
}
}
在 Zig 中,较大的内聚文件是惯用的做法。将相关代码放在一起:测试与实现相邻,编译期泛型在文件作用域,通过 pub 控制公开/私有。仅当文件处理真正独立的问题时才进行拆分。标准库通过像 std/mem.zig 这样的文件展示了这种模式,其中包含 2000 多行内聚的内存操作。
!T)返回带有上下文的错误;每个函数返回一个值或一个错误。显式错误集记录了失败模式。errdefer 在错误路径上进行清理;使用 defer 进行无条件清理。这可以防止资源泄漏,而无需 try-finally 样板代码。switch 语句中的所有分支;包含一个 else 子句,用于返回错误或对真正不可能的情况使用 unreachable。std.testing.allocator 以检测内存泄漏。const 而非 var;为了边界安全,优先使用切片而非原始指针。不可变性传达了意图并支持优化。anytype;优先使用显式的 comptime T: type 参数。显式类型记录了意图并产生更清晰的错误消息。std.log.scoped 进行命名空间日志记录;在模块级别定义 log 常量,以便在整个文件中保持一致的日志作用域。std.testing.allocator 自动捕获内存泄漏。未实现逻辑的显式失败:
fn buildWidget(widget_type: []const u8) !Widget {
return error.NotImplemented;
}
使用 try 传播错误:
fn readConfig(path: []const u8) !Config {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const contents = try file.readToEndAlloc(allocator, max_size);
return parseConfig(contents);
}
使用 errdefer 进行资源清理:
fn createResource(allocator: std.mem.Allocator) !*Resource {
const resource = try allocator.create(Resource);
errdefer allocator.destroy(resource);
resource.* = try initializeResource();
return resource;
}
带有显式默认值的穷举 switch:
fn processStatus(status: Status) ![]const u8 {
return switch (status) {
.active => "processing",
.inactive => "skipped",
_ => error.UnhandledStatus,
};
}
带有内存泄漏检测的测试:
const std = @import("std");
test "widget creation" {
const allocator = std.testing.allocator;
var list: std.ArrayListUnmanaged(u32) = .empty;
defer list.deinit(allocator);
try list.append(allocator, 42);
try std.testing.expectEqual(1, list.items.len);
}
defer。将清理逻辑放在获取旁边以提高清晰度。std.testing.allocator;它会报告泄漏,并显示分配来源的堆栈跟踪。分配器作为显式参数:
fn processData(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
const result = try allocator.alloc(u8, input.len * 2);
errdefer allocator.free(result);
// 将输入处理到结果中
return result;
}
用于批处理操作的竞技场分配器:
fn processBatch(items: []const Item) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
for (items) |item| {
const processed = try processItem(allocator, item);
try outputResult(processed);
}
// 所有分配在竞技场销毁时释放
}
std.log.scoped 创建命名空间日志记录器;每个模块应定义自己的作用域日志记录器以便过滤。const log;在整个模块中一致地使用它。err 用于失败,warn 用于可疑情况,info 用于状态更改,debug 用于跟踪。模块的作用域日志记录器:
const std = @import("std");
const log = std.log.scoped(.widgets);
pub fn createWidget(name: []const u8) !Widget {
log.debug("creating widget: {s}", .{name});
const widget = try allocateWidget(name);
log.debug("created widget id={d}", .{widget.id});
return widget;
}
pub fn deleteWidget(id: u32) void {
log.info("deleting widget id={d}", .{id});
// 清理
}
代码库中的多个作用域:
// 在 src/db.zig 中
const log = std.log.scoped(.db);
// 在 src/http.zig 中
const log = std.log.scoped(.http);
// 在 src/auth.zig 中
const log = std.log.scoped(.auth);
comptime 参数;类型信息在编译时可用,且运行时成本为零。@compileError。带有编译期类型的泛型函数:
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
编译期验证:
fn createBuffer(comptime size: usize) [size]u8 {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
return [_]u8{0} ** size;
}
comptime T: type 而非 anytype;显式类型参数记录了预期的约束并产生更清晰的错误。std.debug.print)或用于回调/闭包时才使用 anytype。anytype 时,添加文档注释描述预期的接口或约束。优先使用显式编译期类型(良好):
fn sum(comptime T: type, items: []const T) T {
var total: T = 0;
for (items) |item| {
total += item;
}
return total;
}
当类型已知时避免使用 anytype(不良):
// 不清楚哪些类型有效;错误消息会令人困惑
fn sum(items: anytype) @TypeOf(items[0]) {
// ...
}
用于回调的可接受的 anytype:
/// 为每个项目调用 `callback`。回调必须接受 (T) 并返回 void。
fn forEach(comptime T: type, items: []const T, callback: anytype) void {
for (items) |item| {
callback(item);
}
}
当必须使用 anytype 时使用 @TypeOf:
fn debugPrint(value: anytype) void {
const T = @TypeOf(value);
if (@typeInfo(T) == .Pointer) {
std.debug.print("ptr: {*}\n", .{value});
} else {
std.debug.print("val: {}\n", .{value});
}
}
anyerror。特定的错误记录了失败模式。catch 进行错误恢复或日志记录;仅当错误确实不可能时才使用 catch unreachable。|| 合并错误集。特定错误集:
const ConfigError = error{
FileNotFound,
ParseError,
InvalidFormat,
};
fn loadConfig(path: []const u8) ConfigError!Config {
// 实现
}
带有 catch 块的错误处理:
const value = operation() catch |err| {
std.log.err("operation failed: {}", .{err});
return error.OperationFailed;
};
std.posix.getenv。类型化配置结构体:
const std = @import("std");
pub const Config = struct {
port: u16,
database_url: []const u8,
api_key: []const u8,
env: []const u8,
};
pub fn loadConfig() !Config {
const db_url = std.posix.getenv("DATABASE_URL") orelse
return error.MissingDatabaseUrl;
const api_key = std.posix.getenv("API_KEY") orelse
return error.MissingApiKey;
const port_str = std.posix.getenv("PORT") orelse "3000";
const port = std.fmt.parseInt(u16, port_str, 10) catch
return error.InvalidPort;
return .{
.port = port,
.database_url = db_url,
.api_key = api_key,
.env = std.posix.getenv("ENV") orelse "development",
};
}
orelse 为可选类型提供默认值;仅当 null 是程序错误时才使用 .?。if (optional) |value| 模式进行安全解包并访问值。安全的可选类型处理:
fn findWidget(id: u32) ?*Widget {
// 查找实现
}
fn processWidget(id: u32) !void {
const widget = findWidget(id) orelse return error.WidgetNotFound;
try widget.process();
}
使用 if 解包可选类型:
if (maybeValue) |value| {
try processValue(value);
} else {
std.log.warn("no value present", .{});
}
参考以下指南了解专门模式:
用于浏览 Zig 标准库和项目依赖文档的 CLI 工具。
安装:
git clone https://github.com/rockorager/zigdoc
cd zigdoc
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local
用法:
zigdoc std.ArrayList # 标准库符号
zigdoc std.mem.Allocator # 嵌套符号
zigdoc vaxis.Window # 项目依赖(如果在 build.zig 中)
zigdoc @init # 创建带有 API 模式的 AGENTS.md
用于 Zig 源代码的 Linter,强制执行编码标准。
安装:
git clone https://github.com/rockorager/ziglint
cd ziglint
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local
用法:
ziglint # 检查当前目录(如果存在则使用 .ziglint.zon)
ziglint src build.zig # 检查特定路径
ziglint --ignore Z001 # 抑制特定规则
配置(.ziglint.zon):
.{
.paths = .{ "src", "build.zig" },
.rules = .{
.Z001 = .{ .enabled = false },
.Z024 = .{ .max_length = 80 },
},
}
内联抑制:
fn MyBadName() void {} // ziglint-ignore: Z001
每周安装次数
167
仓库
GitHub 星标数
37
首次出现
2026年1月20日
安全审计
安装于
opencode143
gemini-cli136
codex134
github-copilot125
claude-code113
amp111
Types define the contract before implementation. Follow this workflow:
Use Zig's type system to prevent invalid states at compile time.
Tagged unions for mutually exclusive states:
// Good: only valid combinations possible
const RequestState = union(enum) {
idle,
loading,
success: []const u8,
failure: anyerror,
};
fn handleState(state: RequestState) void {
switch (state) {
.idle => {},
.loading => showSpinner(),
.success => |data| render(data),
.failure => |err| showError(err),
}
}
// Bad: allows invalid combinations
const RequestState = struct {
loading: bool,
data: ?[]const u8,
err: ?anyerror,
};
Explicit error sets for failure modes:
// Good: documents exactly what can fail
const ParseError = error{
InvalidSyntax,
UnexpectedToken,
EndOfInput,
};
fn parse(input: []const u8) ParseError!Ast {
// implementation
}
// Bad: anyerror hides failure modes
fn parse(input: []const u8) anyerror!Ast {
// implementation
}
Distinct types for domain concepts:
// Prevent mixing up IDs of different types
const UserId = enum(u64) { _ };
const OrderId = enum(u64) { _ };
fn getUser(id: UserId) !User {
// Compiler prevents passing OrderId here
}
fn createUserId(raw: u64) UserId {
return @enumFromInt(raw);
}
Comptime validation for invariants:
fn Buffer(comptime size: usize) type {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
if (size > 1024 * 1024) {
@compileError("buffer size exceeds 1MB limit");
}
return struct {
data: [size]u8 = undefined,
len: usize = 0,
};
}
Non-exhaustive enums for extensibility:
// External enum that may gain variants
const Status = enum(u8) {
active = 1,
inactive = 2,
pending = 3,
_,
};
fn processStatus(status: Status) !void {
switch (status) {
.active => {},
.inactive => {},
.pending => {},
_ => return error.UnknownStatus,
}
}
Larger cohesive files are idiomatic in Zig. Keep related code together: tests alongside implementation, comptime generics at file scope, public/private controlled by pub. Split only when a file handles genuinely separate concerns. The standard library demonstrates this pattern with files like std/mem.zig containing 2000+ lines of cohesive memory operations.
!T); every function returns a value or an error. Explicit error sets document failure modes.errdefer for cleanup on error paths; use defer for unconditional cleanup. This prevents resource leaks without try-finally boilerplate.switch statements; include an else clause that returns an error or uses unreachable for truly impossible cases.std.testing.allocator in tests for leak detection.const over var; prefer slices over raw pointers for bounds safety. Immutability signals intent and enables optimizations.Explicit failure for unimplemented logic:
fn buildWidget(widget_type: []const u8) !Widget {
return error.NotImplemented;
}
Propagate errors with try:
fn readConfig(path: []const u8) !Config {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const contents = try file.readToEndAlloc(allocator, max_size);
return parseConfig(contents);
}
Resource cleanup with errdefer:
fn createResource(allocator: std.mem.Allocator) !*Resource {
const resource = try allocator.create(Resource);
errdefer allocator.destroy(resource);
resource.* = try initializeResource();
return resource;
}
Exhaustive switch with explicit default:
fn processStatus(status: Status) ![]const u8 {
return switch (status) {
.active => "processing",
.inactive => "skipped",
_ => error.UnhandledStatus,
};
}
Testing with memory leak detection:
const std = @import("std");
test "widget creation" {
const allocator = std.testing.allocator;
var list: std.ArrayListUnmanaged(u32) = .empty;
defer list.deinit(allocator);
try list.append(allocator, 42);
try std.testing.expectEqual(1, list.items.len);
}
defer immediately after acquiring a resource. Place cleanup logic next to acquisition for clarity.std.testing.allocator in tests; it reports leaks with stack traces showing allocation origins.Allocator as explicit parameter:
fn processData(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
const result = try allocator.alloc(u8, input.len * 2);
errdefer allocator.free(result);
// process input into result
return result;
}
Arena allocator for batch operations:
fn processBatch(items: []const Item) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
for (items) |item| {
const processed = try processItem(allocator, item);
try outputResult(processed);
}
// All allocations freed when arena deinits
}
std.log.scoped to create namespaced loggers; each module should define its own scoped logger for filtering.const log at the top of the file; use it consistently throughout the module.err for failures, warn for suspicious conditions, info for state changes, debug for tracing.Scoped logger for a module:
const std = @import("std");
const log = std.log.scoped(.widgets);
pub fn createWidget(name: []const u8) !Widget {
log.debug("creating widget: {s}", .{name});
const widget = try allocateWidget(name);
log.debug("created widget id={d}", .{widget.id});
return widget;
}
pub fn deleteWidget(id: u32) void {
log.info("deleting widget id={d}", .{id});
// cleanup
}
Multiple scopes in a codebase:
// In src/db.zig
const log = std.log.scoped(.db);
// In src/http.zig
const log = std.log.scoped(.http);
// In src/auth.zig
const log = std.log.scoped(.auth);
comptime parameters for generic functions; type information is available at compile time with zero runtime cost.@compileError for invalid configurations that should fail the build.Generic function with comptime type:
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
Compile-time validation:
fn createBuffer(comptime size: usize) [size]u8 {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
return [_]u8{0} ** size;
}
comptime T: type over anytype; explicit type parameters document expected constraints and produce clearer errors.anytype only when the function genuinely accepts any type (like std.debug.print) or for callbacks/closures.anytype, add a doc comment describing the expected interface or constraints.Prefer explicit comptime type (good):
fn sum(comptime T: type, items: []const T) T {
var total: T = 0;
for (items) |item| {
total += item;
}
return total;
}
Avoid anytype when type is known (bad):
// Unclear what types are valid; error messages will be confusing
fn sum(items: anytype) @TypeOf(items[0]) {
// ...
}
Acceptable anytype for callbacks:
/// Calls `callback` for each item. Callback must accept (T) and return void.
fn forEach(comptime T: type, items: []const T, callback: anytype) void {
for (items) |item| {
callback(item);
}
}
Using @TypeOf when anytype is necessary:
fn debugPrint(value: anytype) void {
const T = @TypeOf(value);
if (@typeInfo(T) == .Pointer) {
std.debug.print("ptr: {*}\n", .{value});
} else {
std.debug.print("val: {}\n", .{value});
}
}
anyerror when possible. Specific errors document failure modes.catch with a block for error recovery or logging; use catch unreachable only when errors are truly impossible.|| when combining operations that can fail in different ways.Specific error set:
const ConfigError = error{
FileNotFound,
ParseError,
InvalidFormat,
};
fn loadConfig(path: []const u8) ConfigError!Config {
// implementation
}
Error handling with catch block:
const value = operation() catch |err| {
std.log.err("operation failed: {}", .{err});
return error.OperationFailed;
};
std.posix.getenv scattered throughout code.Typed config struct:
const std = @import("std");
pub const Config = struct {
port: u16,
database_url: []const u8,
api_key: []const u8,
env: []const u8,
};
pub fn loadConfig() !Config {
const db_url = std.posix.getenv("DATABASE_URL") orelse
return error.MissingDatabaseUrl;
const api_key = std.posix.getenv("API_KEY") orelse
return error.MissingApiKey;
const port_str = std.posix.getenv("PORT") orelse "3000";
const port = std.fmt.parseInt(u16, port_str, 10) catch
return error.InvalidPort;
return .{
.port = port,
.database_url = db_url,
.api_key = api_key,
.env = std.posix.getenv("ENV") orelse "development",
};
}
orelse to provide default values for optionals; use .? only when null is a program error.if (optional) |value| pattern for safe unwrapping with access to the value.Safe optional handling:
fn findWidget(id: u32) ?*Widget {
// lookup implementation
}
fn processWidget(id: u32) !void {
const widget = findWidget(id) orelse return error.WidgetNotFound;
try widget.process();
}
Optional with if unwrapping:
if (maybeValue) |value| {
try processValue(value);
} else {
std.log.warn("no value present", .{});
}
Reference these guides for specialized patterns:
CLI tool for browsing Zig std library and project dependency docs.
Install:
git clone https://github.com/rockorager/zigdoc
cd zigdoc
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local
Usage:
zigdoc std.ArrayList # std lib symbol
zigdoc std.mem.Allocator # nested symbol
zigdoc vaxis.Window # project dependency (if in build.zig)
zigdoc @init # create AGENTS.md with API patterns
Linter for Zig source code enforcing coding standards.
Install:
git clone https://github.com/rockorager/ziglint
cd ziglint
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local
Usage:
ziglint # lint current directory (uses .ziglint.zon if present)
ziglint src build.zig # lint specific paths
ziglint --ignore Z001 # suppress specific rule
Configuration (.ziglint.zon):
.{
.paths = .{ "src", "build.zig" },
.rules = .{
.Z001 = .{ .enabled = false },
.Z024 = .{ .max_length = 80 },
},
}
Inline suppression:
fn MyBadName() void {} // ziglint-ignore: Z001
Weekly Installs
167
Repository
GitHub Stars
37
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode143
gemini-cli136
codex134
github-copilot125
claude-code113
amp111
Perl 5.36+ 现代开发模式与最佳实践 | 构建健壮可维护应用程序指南
905 周安装
anytype; prefer explicit comptime T: type parameters. Explicit types document intent and produce clearer error messages.std.log.scoped for namespaced logging; define a module-level log constant for consistent scope across the file.std.testing.allocator to catch memory leaks automatically.