错误联合类型 #

一、错误联合类型基础 #

1.1 什么是错误联合类型 #

错误联合类型表示一个值可能是成功的结果,也可能是错误:

zig
const std = @import("std");

pub fn main() void {
    // 错误联合类型
    const result: anyerror!i32 = 42;
    const error_result: anyerror!i32 = error.SomethingWentWrong;
    
    std.debug.print("result: {!}\n", .{result});
    std.debug.print("error_result: {!}\n", .{error_result});
}

1.2 错误集 #

定义错误类型:

zig
const std = @import("std");

const FileError = error{
    NotFound,
    PermissionDenied,
    TooLarge,
    InvalidFormat,
};

fn readFile(path: []const u8) FileError![]const u8 {
    if (path.len == 0) {
        return error.NotFound;
    }
    return "file content";
}

pub fn main() void {
    const result = readFile("test.txt") catch |err| {
        std.debug.print("Error: {}\n", .{err});
        return;
    };
    std.debug.print("Content: {s}\n", .{result});
}

1.3 anyerror #

anyerror 是所有错误的超集:

zig
const std = @import("std");

fn mightFail() anyerror!void {
    return error.OutOfMemory;
}

pub fn main() void {
    mightFail() catch |err| {
        std.debug.print("Caught error: {}\n", .{err});
    };
}

二、错误处理操作符 #

2.1 catch 操作符 #

catch 用于捕获错误并提供默认值:

zig
const std = @import("std");

fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisionByZero;
    return @divTrunc(a, b);
}

pub fn main() void {
    // 提供默认值
    const result1 = divide(10, 2) catch 0;
    const result2 = divide(10, 0) catch 0;
    
    std.debug.print("10 / 2 = {}\n", .{result1});
    std.debug.print("10 / 0 = {}\n", .{result2});
    
    // 处理错误
    const result3 = divide(10, 0) catch |err| blk: {
        std.debug.print("Error occurred: {}\n", .{err});
        break :blk -1;
    };
    std.debug.print("result3 = {}\n", .{result3});
}

2.2 try 操作符 #

try 用于传播错误:

zig
const std = @import("std");

fn innerFunction() !i32 {
    return error.InnerError;
}

fn outerFunction() !i32 {
    // 如果 innerFunction 失败,outerFunction 也会返回错误
    const value = try innerFunction();
    return value * 2;
}

pub fn main() void {
    const result = outerFunction() catch |err| {
        std.debug.print("Error: {}\n", .{err});
        return;
    };
    std.debug.print("Result: {}\n", .{result});
}

2.3 try 与 catch 的区别 #

zig
const std = @import("std");

fn mightFail() !i32 {
    return error.Failure;
}

pub fn main() !void {
    // try: 传播错误给调用者
    const value1 = try mightFail();
    
    // catch: 处理错误
    const value2 = mightFail() catch |err| {
        std.debug.print("Error: {}\n", .{err});
        return;
    };
    
    _ = value1;
    _ = value2;
}

三、errdefer #

3.1 基本用法 #

errdefer 在函数返回错误时执行:

zig
const std = @import("std");

fn allocateAndProcess(allocator: std.mem.Allocator) !void {
    const buffer = try allocator.alloc(u8, 100);
    errdefer allocator.free(buffer);
    
    // 如果后续操作失败,buffer 会被释放
    if (someCondition()) {
        return error.ProcessingFailed;
    }
    
    // 成功时,调用者负责释放
}

fn someCondition() bool {
    return true;
}

pub fn main() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    allocateAndProcess(allocator) catch |err| {
        std.debug.print("Error: {}\n", .{err});
    };
}

3.2 errdefer 与 defer 的区别 #

zig
const std = @import("std");

fn example(success: bool) !void {
    std.debug.print("Start\n", .{});
    defer std.debug.print("defer: Always executed\n", .{});
    errdefer std.debug.print("errdefer: Only on error\n", .{});
    
    if (!success) {
        return error.Failure;
    }
    
    std.debug.print("Success path\n", .{});
}

pub fn main() void {
    std.debug.print("--- Success case ---\n", .{});
    example(true) catch {};
    
    std.debug.print("\n--- Error case ---\n", .{});
    example(false) catch {};
}

四、错误集操作 #

4.1 错误集合并 #

zig
const std = @import("std");

const ErrorA = error{
    NotFound,
    InvalidInput,
};

const ErrorB = error{
    PermissionDenied,
    Timeout,
};

const CombinedError = ErrorA || ErrorB;

fn mightFailA() ErrorA!void {}
fn mightFailB() ErrorB!void {}

fn combined() CombinedError!void {
    try mightFailA();
    try mightFailB();
}

4.2 错误集推断 #

zig
const std = @import("std");

// 编译器自动推断错误集
fn inferredErrors() !void {
    if (false) return error.A;
    if (false) return error.B;
    if (false) return error.C;
}

pub fn main() void {
    inferredErrors() catch |err| {
        std.debug.print("Error: {}\n", .{err});
    };
}

4.3 错误集包含检查 #

zig
const std = @import("std");

const MyError = error{
    NotFound,
    InvalidInput,
};

pub fn main() void {
    const err: MyError = error.NotFound;
    
    if (err == error.NotFound) {
        std.debug.print("Item not found\n", .{});
    }
    
    // 使用 switch
    switch (err) {
        error.NotFound => std.debug.print("Not found\n", .{}),
        error.InvalidInput => std.debug.print("Invalid input\n", .{}),
    }
}

五、错误与可选类型的转换 #

5.1 错误转可选 #

zig
const std = @import("std");

fn mightFail() !i32 {
    return error.Failure;
}

pub fn main() void {
    // 使用 catch null
    const optional: ?i32 = mightFail() catch null;
    
    if (optional) |value| {
        std.debug.print("Value: {}\n", .{value});
    } else {
        std.debug.print("No value\n", .{});
    }
}

5.2 可选转错误 #

zig
const std = @import("std");

fn mightBeNull() ?i32 {
    return null;
}

pub fn main() !void {
    const value = mightBeNull() orelse return error.NoValue;
    std.debug.print("Value: {}\n", .{value});
}

六、实际应用 #

6.1 文件操作 #

zig
const std = @import("std");

fn readFileContent(path: []const u8, allocator: std.mem.Allocator) ![]u8 {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();
    
    const stat = try file.stat();
    const content = try allocator.alloc(u8, stat.size);
    errdefer allocator.free(content);
    
    const bytes_read = try file.readAll(content);
    if (bytes_read != stat.size) {
        return error.IncompleteRead;
    }
    
    return content;
}

pub fn main() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const content = readFileContent("test.txt", allocator) catch |err| {
        std.debug.print("Error reading file: {}\n", .{err});
        return;
    };
    defer allocator.free(content);
    
    std.debug.print("Content: {s}\n", .{content});
}

6.2 网络操作 #

zig
const std = @import("std");

fn connectToServer(address: []const u8, port: u16) !std.net.Stream {
    const addr = try std.net.Address.parseIp(address, port);
    return std.net.tcpConnectToAddress(addr);
}

pub fn main() void {
    const conn = connectToServer("127.0.0.1", 8080) catch |err| {
        std.debug.print("Connection failed: {}\n", .{err});
        return;
    };
    defer conn.close();
    
    std.debug.print("Connected!\n", .{});
}

6.3 解析操作 #

zig
const std = @import("std");

const ParseError = error{
    InvalidFormat,
    OutOfRange,
    EmptyInput,
};

fn parsePositiveInt(input: []const u8) ParseError!u32 {
    if (input.len == 0) return error.EmptyInput;
    
    var result: u32 = 0;
    for (input) |c| {
        if (c < '0' or c > '9') return error.InvalidFormat;
        
        const digit = c - '0';
        if (result > (@as(u32, @intCast(std.math.maxInt(u32)) - digit) / 10)) {
            return error.OutOfRange;
        }
        
        result = result * 10 + digit;
    }
    
    return result;
}

pub fn main() void {
    const inputs = [_][]const u8{ "123", "abc", "", "999999999999999999999" };
    
    for (inputs) |input| {
        const result = parsePositiveInt(input) catch |err| {
            std.debug.print("'{s}': Error - {}\n", .{ input, err });
            continue;
        };
        std.debug.print("'{s}': {}\n", .{ input, result });
    }
}

七、最佳实践 #

7.1 错误命名 #

zig
// 好:描述性的错误名称
const FileError = error{
    NotFound,
    PermissionDenied,
    IsDirectory,
    NotDirectory,
};

// 避免:模糊的错误名称
const BadError = error{
    Failed,
    Error,
    SomethingWrong,
};

7.2 错误粒度 #

zig
// 好:细粒度错误
fn openFile(path: []const u8) !File {
    if (!exists(path)) return error.NotFound;
    if (!hasPermission(path)) return error.PermissionDenied;
    return open(path);
}

// 避免:粗粒度错误
fn openFileBad(path: []const u8) !File {
    if (!canOpen(path)) return error.CannotOpen;
    return open(path);
}

7.3 错误处理策略 #

zig
const std = @import("std");

fn processItem(item: Item) !void {
    // 策略1:传播错误
    const data = try loadData(item);
    
    // 策略2:提供默认值
    const config = loadConfig() catch defaultConfig();
    
    // 策略3:记录并继续
    if (validate(item)) |_| {
        // 验证通过
    } else |err| {
        std.debug.print("Validation warning: {}\n", .{err});
    }
    
    _ = data;
    _ = config;
}

const Item = struct {};
fn loadData(item: Item) ![]const u8 { _ = item; return "data"; }
fn loadConfig() ![]const u8 { return error.NoConfig; }
fn defaultConfig() []const u8 { return "default"; }
fn validate(item: Item) !void { _ = item; return; }

八、总结 #

错误联合类型要点:

操作 说明
!T 错误联合类型
error{} 错误集定义
try 传播错误
catch 捕获错误
errdefer 错误时执行

下一步,让我们学习控制流!

最后更新:2026-03-27