Skip to content

Zig 编译模型教程

Zig 编译基于模块组织,每个模块是一个 Zig 源文件的集合:

// main.zig - 根模块(主模块)
const std = @import("std"); // 导入标准库模块
pub fn main() void {
std.debug.print("Hello, World!\n", .{});
}
// 模块依赖关系形成有向图(允许循环依赖)
  • 每个模块有一个根源文件
  • 模块可以依赖其他模块(通过 @import)
  • 隐式依赖标准库:所有模块都能 @import("std")
  • 隐式依赖根模块:所有模块都能 @import("root")
项目结构:
src/
├── main.zig # 根模块
├── utils/ # 工具模块
│ ├── math.zig # 数学函数
│ └── strings.zig # 字符串处理
└── network/ # 网络模块
└── http.zig # HTTP 客户端
main.zig
const std = @import("std");
const math = @import("utils/math.zig"); // 导入其他模块
const http = @import("network/http.zig");
// network/http.zig
const strings = @import("../utils/strings.zig"); // 相对路径导入
const root = @import("root"); // 引用根模块

每个 Zig 源文件隐式是一个结构体:

// Point.zig - 包含字段的文件,应按类型命名(驼峰式)
x: f32,
y: f32,
// 使用 @This() 引用文件自身的类型
const Point = @This();
pub fn init(x_val: f32, y_val: f32) Point {
return .{
.x = x_val,
.y = y_val,
};
}
pub fn distance(self: Point, other: Point) f32 {
const dx = self.x - other.x;
const dy = self.y - other.y;
return @sqrt(dx * dx + dy * dy);
}
main.zig
const Point = @import("Point.zig");
pub fn main() void {
// 实例化文件结构体
const p1 = Point.init(1.0, 2.0);
const p2 = Point{ .x = 3.0, .y = 4.0 }; // 直接构造
const dist = p1.distance(p2);
std.debug.print("Distance: {}\n", .{dist});
}
// Constants.zig - 不包含字段,按常量命名(蛇形)
pub const PI = 3.141592653589793;
pub const E = 2.718281828459045;
// 不需要 @This(),因为没有字段
// 可以直接作为命名空间使用

Zig 编译器按需分析代码,遵循以下递归规则:

  1. @import 调用 → 分析被导入的文件
  2. 类型分析 → 分析其中的 comptime 和 export 声明
  3. 测试编译 → 根模块中的测试声明会被分析
  4. 引用声明 → 分析被引用的声明(顺序无关)
编译开始
↓
分析 std/std.zig(标准库根)
↓
分析 std/start.zig
↓
@import("root") → 分析用户根模块
↓
根据规则递归发现其他代码
// 确保特定文件被分析(用于测试和导出)
comptime {
// 强制发现 API 文件
_ = @import("api.zig");
// 条件发现
if (@import("builtin").os.tag == .windows) {
_ = @import("windows_specific.zig");
}
}
test {
// 确保测试文件被发现
_ = @import("extra_tests.zig");
// 平台特定测试
if (builtin.cpu.arch == .x86_64) {
_ = @import("x64_tests.zig");
}
}
// main.zig - 标准入口点
const std = @import("std");
/// 程序入口点,支持多种返回类型:
/// void, error!void, u8, error!u8
pub fn main() !void {
std.debug.print("程序启动\n", .{});
const result = try processData();
std.debug.print("结果: {}\n", .{result});
// 返回 void → 退出码 0
// 返回 u8 → 该值作为退出码
// 返回错误 → 打印错误跟踪,退出码 1
}
fn processData() !i32 {
// ... 业务逻辑
return 42;
}
// 禁用标准启动逻辑,提供低级入口点
pub const _start = {};
// 现在需要自己定义入口点
export fn _start() callconv(.naked) noreturn {
// 低级系统入口(无标准库初始化)
asm volatile (
\\ syscall
:
: [number] "{rax}" (1), // write
[fd] "{rdi}" (1), // stdout
[buf] "{rsi}" (@ptrFromInt(&message)),
[count] "{rdx}" (@intCast(message.len))
: "rcx", "r11", "memory"
);
asm volatile (
\\ syscall
:
: [number] "{rax}" (60), // exit
[code] "{rdi}" (0)
: "rcx", "r11", "memory"
);
unreachable;
}
const message = "Hello from custom entry!\n";
// 链接 libc 时的传统 main 函数
pub export fn main(argc: c_int, argv: [*]const [*:0]const u8) c_int {
const args = argv[0..@intCast(argc)];
if (argc > 1) {
std.debug.print("参数1: {s}\n", .{args[1]});
}
return 0; // 返回退出码
}
// 编译: zig build-exe main.zig -lc
// 自定义标准库行为
pub const std_options = struct {
// 启用/禁用段错误处理程序
.enable_segfault_handler = true,
// 自定义日志实现
.logFn = myCustomLogger,
// 默认日志级别
.log_level = .info,
// 启用代码覆盖率
.enable_coverage = true,
};
fn myCustomLogger(
comptime level: std.log.Level,
comptime scope: @Type(.enum_literal),
comptime format: []const u8,
args: anytype,
) void {
// 自定义日志格式
const prefix = switch (level) {
.err => "❌ ERROR",
.warn => "⚠️ WARNING",
.info => "ℹ️ INFO",
.debug => "🐛 DEBUG",
};
std.debug.print("[{s}] {s}: " ++ format ++ "\n", .{
prefix, @tagName(scope)
} ++ args);
}
pub const std_options = std.Options{
// 运行时安全检查
.runtime_safety = builtin.mode == .Debug,
// 标准输出流(Windows 控制台)
.stdio_is_utf8 = true,
// 标准库分配器
.gpa_allocator = &std.heap.GeneralPurposeAllocator(.{}){},
// 测试相关
.test_runner = myTestRunner,
};
fn myTestRunner(tests: []const std.builtin.Test) !void {
std.debug.print("运行 {} 个测试...\n", .{tests.len});
return std.testing.defaultTestRunner(tests);
}
pub const panic = std.debug.simple_panic; // 使用简单实现
// 或完全自定义
pub const panic = struct {
pub fn panic(
message: []const u8,
trace: ?*std.builtin.StackTrace,
ret_addr: ?usize,
) noreturn {
_ = trace;
_ = ret_addr;
std.debug.print("💥 程序崩溃: {s}\n", .{message});
std.os.exit(0xff); // 非零退出码
}
};
// 使用 FullPanic 保持安全检查
pub const panic = std.debug.FullPanic(customPanic);
fn customPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
_ = first_trace_addr;
// 写入错误日志
const error_log = "error.log";
const file = std.fs.cwd().createFile(error_log, .{}) catch {
// 无法记录,直接退出
std.process.exit(0xff);
};
defer file.close();
file.writer().print("Panic at {}: {s}\n", .{
std.time.timestamp(), msg
}) catch {};
// 在控制台显示友好信息
std.debug.print(
\\===================================
\\ 程序遇到错误,详情已记录到 {s}
\\===================================
\\
, .{error_log});
std.process.exit(1);
}
my_project/
├── build.zig # 构建脚本
├── src/
│ ├── main.zig # 根模块
│ ├── lib/
│ │ ├── math.zig # 数学库模块
│ │ └── data.zig # 数据结构模块
│ └── app/
│ ├── ui.zig # UI 模块
│ └── logic.zig # 业务逻辑模块
└── tests/
└── integration.zig # 集成测试
build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 创建可执行文件
const exe = b.addExecutable(.{
.name = "my_project",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// 添加模块(如果使用包管理器)
// exe.addModule("math", .{ .source_file = .{ .path = "src/lib/math.zig" } });
b.installArtifact(exe);
}
// config.zig - 配置模块
pub const Config = struct {
pub const debug_mode = true;
pub const enable_logging = true;
pub const max_connections = 100;
};
// main.zig - 使用条件编译
const config = @import("config.zig");
comptime {
// 根据配置发现不同的模块
if (config.debug_mode) {
_ = @import("debug/debug_tools.zig");
}
if (config.enable_logging) {
_ = @import("logging/logger.zig");
}
}
test {
// 发现所有测试
_ = @import("tests/unit_tests.zig");
if (config.debug_mode) {
_ = @import("tests/debug_tests.zig");
}
}
// 多入口点支持
pub const EntryPoint = enum {
cli, // 命令行界面
gui, // 图形界面
server, // 服务器模式
};
// 根据编译目标选择入口点
pub const entry_mode: EntryPoint = if (@import("builtin").os.tag == .windows)
EntryPoint.gui
else
EntryPoint.cli;
// 条件入口点
pub fn main() !void {
switch (entry_mode) {
.cli => try cliMain(),
.gui => try guiMain(),
.server => try serverMain(),
}
}
fn cliMain() !void {
std.debug.print("命令行模式\n", .{});
// 处理命令行参数...
}
// 条件编译不同的入口实现
comptime {
if (entry_mode == .gui) {
_ = @import("gui/entry.zig");
} else if (entry_mode == .server) {
_ = @import("server/entry.zig");
}
}
// meta_builder.zig - 编译时模块构建器
pub fn buildModule(comptime module_name: []const u8) type {
return struct {
pub const name = module_name;
pub fn init() void {
std.debug.print("初始化模块: {s}\n", .{name});
}
// 动态生成函数
pub const functions = struct {
pub const @"print_name" = struct {
pub fn call() void {
std.debug.print("模块: {s}\n", .{name});
}
};
};
};
}
// 使用
comptime {
const math_module = buildModule("数学模块");
const data_module = buildModule("数据模块");
// 这些模块现在可用
_ = math_module;
_ = data_module;
}
plugin_system.zig
const Plugin = struct {
name: []const u8,
init: fn () void,
deinit: fn () void,
};
// 收集所有插件
var plugins: std.ArrayList(Plugin) = undefined;
comptime {
plugins = std.ArrayList(Plugin).init(std.heap.page_allocator);
// 自动发现并注册插件
inline for (@typeInfo(@import("root")).Struct.decls) |decl| {
if (std.mem.startsWith(u8, decl.name, "plugin_")) {
const plugin = @field(@import("root"), decl.name);
plugins.append(plugin) catch unreachable;
}
}
}
pub fn initializePlugins() void {
for (plugins.items) |plugin| {
plugin.init();
}
}
  1. 模块设计

    • 单一职责:每个模块做一件事
    • 清晰依赖:避免循环依赖
    • 合理命名:文件结构体用驼峰式,纯声明文件用蛇形
  2. 入口点管理

    • 默认使用标准 main 函数
    • 特殊需求时使用 _start
    • C 兼容程序使用 export fn main
  3. 配置标准库

    • 通过 std_options 定制行为
    • 提供合适的恐慌处理
    • 根据需要自定义日志
  4. 代码发现

    • 使用 comptime 块强制发现重要文件
    • 测试文件放在测试块中
    • 条件编译减少不必要的发现
  5. 构建管理

    • 使用 build.zig 管理复杂项目
    • 合理组织目录结构
    • 考虑跨平台兼容性

通过深入理解 Zig 的编译模型,你可以更好地组织大型项目,实现灵活的编译时配置,并构建高效可靠的应用程序。