Zig的错误集是编译期确定的枚举——没有异常,没有错误码,没有Result类型包装:
const std = @import("std");
// 定义错误集
const FileError = error{
NotFound,
PermissionDenied,
IsDir,
NotDir,
FileTooBig,
DiskFull,
};
// 错误集可以合并
const NetworkError = error{
ConnectionRefused,
Timeout,
DnsFailed,
};
const AppError = FileError || NetworkError;
// anyerror: 所有可能错误的超集
fn riskyOperation() anyerror!void {
// 可以返回任何错误
}
// 错误集推断
fn readConfig() !void {
// 编译器自动推断错误集
const file = try std.fs.cwd().openFile("config.toml", .{});
defer file.close();
}
错误联合类型E!T表示"要么是T,要么是E中的某个错误":
// E!T = 错误联合类型
const result: FileError!usize = 42; // 成功
const err: FileError!usize = error.NotFound; // 失败
// 获取大小
fn getFileSize(path: []const u8) !u64 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const stat = try file.stat();
return stat.size;
}
// 错误联合类型与可选类型组合
fn findUser(id: u32) !?User {
// 可能出错、可能找不到、可能找到
const row = try db.query("SELECT * FROM users WHERE id=?", .{id});
if (!row.next()) return null;
return row.toUser();
}
const std = @import("std");
pub fn main() !void {
// try: 成功返回值,失败传播错误
const file = try std.fs.cwd().openFile("data.txt", .{});
// 等价于:
// const file = std.fs.cwd().openFile("data.txt", .{}) catch |err| return err;
// catch: 处理错误
const size = getFileSize("data.txt") catch |err| switch (err) {
error.FileNotFound => 0,
error.PermissionDenied => 0,
else => return err, // 重新传播
};
// catch提供默认值
const config = readConfig() catch defaultConfig;
// catch + 副作用
const conn = connect() catch |err| {
std.debug.print("连接失败: {}\n", .{err});
return err;
};
_ = file;
_ = size;
_ = config;
_ = conn;
}
// 用catch实现重试
fn connectWithRetry(addr: []const u8, max_retries: u32) !Connection {
var retries: u32 = 0;
while (retries < max_retries) : (retries += 1) {
return connect(addr) catch |err| switch (err) {
error.ConnectionRefused => {
std.debug.print("重试 {}/{}...\n", .{ retries + 1, max_retries });
continue;
},
else => return err,
};
}
return error.MaxRetriesExceeded;
}
// errdefer: 只在错误路径执行的清理代码
fn createUser(allocator: std.mem.Allocator, name: []const u8) !*User {
// 分配用户结构体
const user = try allocator.create(User);
errdefer allocator.destroy(user); // 只在出错时销毁
// 分配名字的副本
const name_copy = try allocator.dupe(u8, name);
errdefer allocator.free(name_copy); // 只在出错时释放
user.* = .{
.name = name_copy,
.created = std.time.timestamp(),
};
// 成功!user和name_copy由调用者管理
return user;
}
// errdefer + 错误值捕获
fn initSystem() !void {
errdefer |err| {
std.debug.print("初始化失败: {}\n", .{err});
cleanupPartial();
}
try initNetwork();
try initDatabase();
try initCache();
}
Zig提供错误跟踪(Error Trace)来帮助调试错误传播链:
// 编译时启用错误跟踪
// zig build -Derror-tracing=true
// 或者运行时
pub fn main() !void {
deepCall() catch |err| {
std.debug.print("错误: {}\n", .{err});
// 打印完整错误跟踪
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
};
}
// 自定义错误消息
fn deepCall() !void {
try level1();
}
fn level1() !void {
try level2();
}
fn level2() !void {
return error.DeepFailure;
}
// 底层:具体错误集
const DbError = error{ ConnectionLost, QueryFailed, Timeout };
const CacheError = error{ Miss, Expired, Corrupted };
// 高层:合并错误集
fn getUser(id: u32) (DbError || CacheError)!User {
// 先查缓存
const cached = cacheGet(id) catch |err| switch (err) {
error.Miss, error.Expired => {
// 缓存未命中,查数据库
return dbGet(id);
},
error.Corrupted => {
// 缓存损坏,清除后查数据库
cacheInvalidate(id);
return dbGet(id);
},
};
return cached;
}
// 不可恢复:用std.debug.panic
fn criticalInit() void {
const config = loadConfig() catch |err| {
std.debug.panic("无法加载配置: {}", .{err});
};
}
// 可恢复:传播错误
fn optionalInit() !void {
const config = loadConfig() catch |err| switch (err) {
error.FileNotFound => defaultConfig(),
else => return err,
};
}
| 方式 | 时间(ms) | 说明 |
|---|---|---|
| Zig try (成功路径) | 1.2 | 几乎零开销 |
| Zig catch (错误路径) | 3.5 | 分支预测惩罚 |
| C errno (成功路径) | 1.3 | 线程局部存储访问 |
| C++ try/catch (成功路径) | 0.8 | 零成本异常 |
| C++ try/catch (异常路径) | 850 | 栈展开开销巨大 |
| Rust Result (成功) | 1.2 | 与Zig类似 |
| Go error (成功) | 2.5 | if err != nil 开销 |
retry(comptime max: u32, func)try传播,观察错误跟踪