zig-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZig Best Practices
Zig 最佳实践
Type-First Development
类型优先开发
Types define the contract before implementation. Follow this workflow:
- Define data structures - structs, unions, and error sets first
- Define function signatures - parameters, return types, and error unions
- Implement to satisfy types - let the compiler guide completeness
- Validate at comptime - catch invalid configurations during compilation
类型在实现前就定义好契约。遵循以下工作流程:
- 定义数据结构 - 先定义结构体、联合类型和错误集
- 定义函数签名 - 参数、返回类型和错误联合类型
- 按类型要求实现 - 让编译器引导你完成完整实现
- 编译时验证 - 在编译阶段捕获无效配置
Make Illegal States Unrepresentable
让非法状态无法被表示
Use Zig's type system to prevent invalid states at compile time.
Tagged unions for mutually exclusive states:
zig
// 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:
zig
// 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:
zig
// 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:
zig
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:
zig
// 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,
}
}利用Zig的类型系统在编译时防止非法状态。
使用标记联合表示互斥状态:
zig
// 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,
};显式错误集定义失败模式:
zig
// 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
}为领域概念使用不同类型:
zig
// 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);
}编译时验证不变量:
zig
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,
};
}非穷举枚举实现可扩展性:
zig
// 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,
}
}Module Structure
模块结构
Larger cohesive files are idiomatic in Zig. Keep related code together: tests alongside implementation, comptime generics at file scope, public/private controlled by . Split only when a file handles genuinely separate concerns. The standard library demonstrates this pattern with files like containing 2000+ lines of cohesive memory operations.
pubstd/mem.zig在Zig中,将相关代码集中在较大的内聚文件里是惯用做法。把相关代码放在一起:测试代码与实现代码相邻,编译时泛型放在文件作用域,通过控制公共/私有访问。只有当文件处理完全无关的功能时才需要拆分。标准库就展示了这种模式,比如包含2000多行内聚的内存操作代码。
pubstd/mem.zigInstructions
规范说明
- Return errors with context using error unions (); every function returns a value or an error. Explicit error sets document failure modes.
!T - Use for cleanup on error paths; use
errdeferfor unconditional cleanup. This prevents resource leaks without try-finally boilerplate.defer - Handle all branches in statements; include an
switchclause that returns an error or useselsefor truly impossible cases.unreachable - Pass allocators explicitly to functions requiring dynamic memory; prefer in tests for leak detection.
std.testing.allocator - Prefer over
const; prefer slices over raw pointers for bounds safety. Immutability signals intent and enables optimizations.var - Avoid ; prefer explicit
anytypeparameters. Explicit types document intent and produce clearer error messages.comptime T: type - Use for namespaced logging; define a module-level
std.log.scopedconstant for consistent scope across the file.log - Add or update tests for new logic; use to catch memory leaks automatically.
std.testing.allocator
- 使用错误联合类型()返回带上下文的错误;每个函数要么返回值,要么返回错误。显式错误集可以明确失败模式。
!T - 在错误路径上使用进行清理;使用
errdefer进行无条件清理。这样无需try-finally样板代码就能防止资源泄漏。defer - 处理语句中的所有分支;对于真正不可能的情况,可以包含返回错误或使用
switch的unreachable分支。else - 为需要动态内存的函数显式传递分配器;在测试中优先使用进行泄漏检测。
std.testing.allocator - 优先使用而非
const;优先使用切片而非原始指针以保证边界安全。不可变性能明确意图并支持优化。var - 避免使用;优先使用显式的
anytype参数。显式类型能明确意图并生成更清晰的错误信息。comptime T: type - 使用实现命名空间日志;在文件中定义模块级别的
std.log.scoped常量以保证一致的作用域。log - 为新逻辑添加或更新测试;使用自动捕获内存泄漏。
std.testing.allocator
Examples
示例
Explicit failure for unimplemented logic:
zig
fn buildWidget(widget_type: []const u8) !Widget {
return error.NotImplemented;
}Propagate errors with try:
zig
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:
zig
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:
zig
fn processStatus(status: Status) ![]const u8 {
return switch (status) {
.active => "processing",
.inactive => "skipped",
_ => error.UnhandledStatus,
};
}Testing with memory leak detection:
zig
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);
}为未实现的逻辑返回显式错误:
zig
fn buildWidget(widget_type: []const u8) !Widget {
return error.NotImplemented;
}使用try传播错误:
zig
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进行资源清理:
zig
fn createResource(allocator: std.mem.Allocator) !*Resource {
const resource = try allocator.create(Resource);
errdefer allocator.destroy(resource);
resource.* = try initializeResource();
return resource;
}带显式默认分支的穷举switch:
zig
fn processStatus(status: Status) ![]const u8 {
return switch (status) {
.active => "processing",
.inactive => "skipped",
_ => error.UnhandledStatus,
};
}带内存泄漏检测的测试:
zig
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);
}Memory Management
内存管理
- Pass allocators explicitly; never use global state for allocation. Functions declare their allocation needs in parameters.
- Use immediately after acquiring a resource. Place cleanup logic next to acquisition for clarity.
defer - Prefer arena allocators for temporary allocations; they free everything at once when the arena is destroyed.
- Use in tests; it reports leaks with stack traces showing allocation origins.
std.testing.allocator
- 显式传递分配器;绝不为内存分配使用全局状态。函数需在参数中声明其内存分配需求。
- 获取资源后立即使用。将清理逻辑放在获取资源的代码旁边以提高可读性。
defer - 临时内存分配优先使用arena分配器;销毁arena时会一次性释放所有内存。
- 在测试中使用;它会通过栈跟踪报告泄漏的内存分配来源。
std.testing.allocator
Examples
示例
Allocator as explicit parameter:
zig
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:
zig
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
}将分配器作为显式参数:
zig
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分配器处理批量操作:
zig
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
}Logging
日志
- Use to create namespaced loggers; each module should define its own scoped logger for filtering.
std.log.scoped - Define a module-level at the top of the file; use it consistently throughout the module.
const log - Use appropriate log levels: for failures,
errfor suspicious conditions,warnfor state changes,infofor tracing.debug
- 使用创建命名空间日志器;每个模块应定义自己的作用域日志器以便过滤。
std.log.scoped - 在文件顶部定义模块级别的;在整个模块中一致地使用它。
const log - 使用合适的日志级别:表示失败,
err表示可疑情况,warn表示状态变更,info表示跟踪信息。debug
Examples
示例
Scoped logger for a module:
zig
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:
zig
// 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);为模块定义作用域日志器:
zig
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
}代码库中的多个作用域:
zig
// 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 Patterns
编译时模式
- Use parameters for generic functions; type information is available at compile time with zero runtime cost.
comptime - Prefer compile-time validation over runtime checks when possible. Catch errors during compilation rather than in production.
- Use for invalid configurations that should fail the build.
@compileError
- 为泛型函数使用参数;类型信息在编译时可用,且无运行时开销。
comptime - 尽可能优先使用编译时验证而非运行时检查。在编译阶段捕获错误而非在生产环境中。
- 使用处理会导致构建失败的无效配置。
@compileError
Examples
示例
Generic function with comptime type:
zig
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}Compile-time validation:
zig
fn createBuffer(comptime size: usize) [size]u8 {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
return [_]u8{0} ** size;
}带编译时类型参数的泛型函数:
zig
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}编译时验证:
zig
fn createBuffer(comptime size: usize) [size]u8 {
if (size == 0) {
@compileError("buffer size must be greater than 0");
}
return [_]u8{0} ** size;
}Avoiding anytype
避免使用anytype
- Prefer over
comptime T: type; explicit type parameters document expected constraints and produce clearer errors.anytype - Use only when the function genuinely accepts any type (like
anytype) or for callbacks/closures.std.debug.print - When using , add a doc comment describing the expected interface or constraints.
anytype
- 优先使用而非
comptime T: type;显式类型参数能明确预期约束并生成更清晰的错误信息。anytype - 仅当函数确实能接受任意类型(如)或用于回调/闭包时才使用
std.debug.print。anytype - 使用时,添加文档注释描述预期的接口或约束。
anytype
Examples
示例
Prefer explicit comptime type (good):
zig
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):
zig
// Unclear what types are valid; error messages will be confusing
fn sum(items: anytype) @TypeOf(items[0]) {
// ...
}Acceptable anytype for callbacks:
zig
/// 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:
zig
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});
}
}优先使用显式编译时类型(推荐):
zig
fn sum(comptime T: type, items: []const T) T {
var total: T = 0;
for (items) |item| {
total += item;
}
return total;
}当类型已知时避免使用anytype(不推荐):
zig
// Unclear what types are valid; error messages will be confusing
fn sum(items: anytype) @TypeOf(items[0]) {
// ...
}回调场景下合理使用anytype:
zig
/// 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);
}
}当必须使用anytype时结合@TypeOf:
zig
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});
}
}Error Handling Patterns
错误处理模式
- Define specific error sets for functions; avoid when possible. Specific errors document failure modes.
anyerror - Use with a block for error recovery or logging; use
catchonly when errors are truly impossible.catch unreachable - Merge error sets with when combining operations that can fail in different ways.
||
- 为函数定义特定的错误集;尽可能避免使用。特定错误集能明确失败模式。
anyerror - 使用块进行错误恢复或日志记录;仅当错误确实不可能发生时才使用
catch。catch unreachable - 当组合可能以不同方式失败的操作时,使用合并错误集。
||
Examples
示例
Specific error set:
zig
const ConfigError = error{
FileNotFound,
ParseError,
InvalidFormat,
};
fn loadConfig(path: []const u8) ConfigError!Config {
// implementation
}Error handling with catch block:
zig
const value = operation() catch |err| {
std.log.err("operation failed: {}", .{err});
return error.OperationFailed;
};特定错误集:
zig
const ConfigError = error{
FileNotFound,
ParseError,
InvalidFormat,
};
fn loadConfig(path: []const u8) ConfigError!Config {
// implementation
}使用catch块处理错误:
zig
const value = operation() catch |err| {
std.log.err("operation failed: {}", .{err});
return error.OperationFailed;
};Configuration
配置管理
- Load config from environment variables at startup; validate required values before use. Missing config should cause a clean exit with a descriptive message.
- Define a Config struct as single source of truth; avoid scattered throughout code.
std.posix.getenv - Use sensible defaults for development; require explicit values for production secrets.
- 在启动时从环境变量加载配置;使用前验证必填值。缺失配置应导致程序干净退出并给出描述性消息。
- 定义Config结构体作为单一可信数据源;避免在代码中分散使用。
std.posix.getenv - 为开发环境使用合理的默认值;生产环境的密钥需要显式配置。
Examples
示例
Typed config struct:
zig
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",
};
}带类型的配置结构体:
zig
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",
};
}Optionals
可选类型
- Use to provide default values for optionals; use
orelseonly when null is a program error..? - Prefer pattern for safe unwrapping with access to the value.
if (optional) |value|
- 使用为可选类型提供默认值;仅当null是程序错误时才使用
orelse。.? - 优先使用模式安全地解包并访问值。
if (optional) |value|
Examples
示例
Safe optional handling:
zig
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:
zig
if (maybeValue) |value| {
try processValue(value);
} else {
std.log.warn("no value present", .{});
}安全处理可选类型:
zig
fn findWidget(id: u32) ?*Widget {
// lookup implementation
}
fn processWidget(id: u32) !void {
const widget = findWidget(id) orelse return error.WidgetNotFound;
try widget.process();
}使用if解包可选类型:
zig
if (maybeValue) |value| {
try processValue(value);
} else {
std.log.warn("no value present", .{});
}Advanced Topics
高级主题
Reference these guides for specialized patterns:
- Building custom containers (queues, stacks, trees): See GENERICS.md
- Interfacing with C libraries (raylib, SDL, curl, system APIs): See C-INTEROP.md
- Debugging memory leaks (GPA, stack traces): See DEBUGGING.md
参考以下指南了解专业模式:
- 构建自定义容器(队列、栈、树):参见 GENERICS.md
- 与C库交互(raylib、SDL、curl、系统API):参见 C-INTEROP.md
- 调试内存泄漏(GPA、栈跟踪):参见 DEBUGGING.md
Tooling
工具链
zigdoc - Documentation Lookup
zigdoc - 文档查询
CLI tool for browsing Zig std library and project dependency docs.
Install:
bash
git clone https://github.com/rockorager/zigdoc
cd zigdoc
zig build install -Doptimize=ReleaseFast --prefix $HOME/.localUsage:
bash
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用于浏览Zig标准库和项目依赖文档的CLI工具。
安装:
bash
git clone https://github.com/rockorager/zigdoc
cd zigdoc
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local使用:
bash
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 patternsziglint - Static Analysis
ziglint - 静态分析
Linter for Zig source code enforcing coding standards.
Install:
bash
git clone https://github.com/rockorager/ziglint
cd ziglint
zig build install -Doptimize=ReleaseFast --prefix $HOME/.localUsage:
bash
ziglint # lint current directory (uses .ziglint.zon if present)
ziglint src build.zig # lint specific paths
ziglint --ignore Z001 # suppress specific ruleConfiguration ():
.ziglint.zonzig
.{
.paths = .{ "src", "build.zig" },
.rules = .{
.Z001 = .{ .enabled = false },
.Z024 = .{ .max_length = 80 },
},
}Inline suppression:
zig
fn MyBadName() void {} // ziglint-ignore: Z001用于Zig源代码的代码检查工具,强制遵循编码规范。
安装:
bash
git clone https://github.com/rockorager/ziglint
cd ziglint
zig build install -Doptimize=ReleaseFast --prefix $HOME/.local使用:
bash
ziglint # lint current directory (uses .ziglint.zon if present)
ziglint src build.zig # lint specific paths
ziglint --ignore Z001 # suppress specific rule配置():
.ziglint.zonzig
.{
.paths = .{ "src", "build.zig" },
.rules = .{
.Z001 = .{ .enabled = false },
.Z024 = .{ .max_length = 80 },
},
}内联抑制:
zig
fn MyBadName() void {} // ziglint-ignore: Z001References
参考资料
- Language Reference: https://ziglang.org/documentation/0.15.2/
- Standard Library: https://ziglang.org/documentation/0.15.2/std/
- Code Samples: https://ziglang.org/learn/samples/
- Zig Guide: https://zig.guide/