Skip to content

Zig 错误处理

Zig 的错误处理系统是其最重要的特性之一,它提供了一种安全、明确且高效的方式来处理错误。

错误的基本概念

什么是错误?

在 Zig 中,错误是一种特殊的值类型,用于表示可能出现的异常情况:

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

// 定义错误集合
const FileError = error{
    NotFound,
    PermissionDenied,
    OutOfMemory,
};

pub fn main() void {
    std.debug.print("错误处理示例\n", .{});
}

错误联合类型

错误联合类型使用 ! 语法,表示函数可能返回错误或正常值:

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

const MathError = error{
    DivisionByZero,
    Overflow,
};

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

pub fn main() void {
    const result1 = divide(10, 2);
    const result2 = divide(10, 0);
    
    std.debug.print("10 / 2 的结果类型: {}\n", .{@TypeOf(result1)});
    std.debug.print("10 / 0 的结果类型: {}\n", .{@TypeOf(result2)});
}

错误处理方式

使用 if 处理错误

最基本的错误处理方式是使用 if 语句:

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

const ParseError = error{
    InvalidFormat,
    OutOfRange,
};

fn parseNumber(input: []const u8) ParseError!i32 {
    if (std.mem.eql(u8, input, "42")) {
        return 42;
    } else if (std.mem.eql(u8, input, "100")) {
        return 100;
    } else if (std.mem.eql(u8, input, "999")) {
        return ParseError.OutOfRange;
    } else {
        return ParseError.InvalidFormat;
    }
}

pub fn main() void {
    const inputs = [_][]const u8{ "42", "100", "999", "abc" };
    
    for (inputs) |input| {
        if (parseNumber(input)) |number| {
            std.debug.print("解析成功: '{s}' -> {}\n", .{ input, number });
        } else |err| {
            std.debug.print("解析失败: '{s}' -> {}\n", .{ input, err });
        }
    }
}

使用 catch 处理错误

catch 操作符可以提供默认值或执行错误处理逻辑:

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

const NumberError = error{
    InvalidInput,
    TooLarge,
};

fn validateNumber(num: i32) NumberError!i32 {
    if (num < 0) return NumberError.InvalidInput;
    if (num > 100) return NumberError.TooLarge;
    return num;
}

pub fn main() void {
    const numbers = [_]i32{ 50, -10, 150, 25 };
    
    for (numbers) |num| {
        // 使用 catch 提供默认值
        const safe_num = validateNumber(num) catch 0;
        std.debug.print("数字 {} -> 安全值 {}\n", .{ num, safe_num });
        
        // 使用 catch 执行错误处理
        const result = validateNumber(num) catch |err| blk: {
            std.debug.print("验证失败: {} (错误: {})\n", .{ num, err });
            break :blk -1;
        };
        std.debug.print("处理结果: {}\n", .{result});
    }
}

使用 try 传播错误

try 操作符用于传播错误到调用者:

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

const ValidationError = error{
    TooSmall,
    TooLarge,
    InvalidRange,
};

fn validateRange(min: i32, max: i32) ValidationError!void {
    if (min < 0) return ValidationError.TooSmall;
    if (max > 1000) return ValidationError.TooLarge;
    if (min >= max) return ValidationError.InvalidRange;
}

fn processRange(min: i32, max: i32) ValidationError!i32 {
    // 使用 try 传播错误
    try validateRange(min, max);
    
    // 如果没有错误,继续处理
    return (min + max) / 2;
}

pub fn main() void {
    const ranges = [_][2]i32{
        [_]i32{ 10, 20 },
        [_]i32{ -5, 15 },
        [_]i32{ 10, 2000 },
        [_]i32{ 50, 30 },
    };
    
    for (ranges) |range| {
        const min = range[0];
        const max = range[1];
        
        if (processRange(min, max)) |result| {
            std.debug.print("范围 [{}, {}] 的中点: {}\n", .{ min, max, result });
        } else |err| {
            std.debug.print("范围 [{}, {}] 无效: {}\n", .{ min, max, err });
        }
    }
}

错误集合

定义错误集合

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

// 文件操作错误
const FileError = error{
    NotFound,
    PermissionDenied,
    AlreadyExists,
    DiskFull,
};

// 网络错误
const NetworkError = error{
    ConnectionFailed,
    Timeout,
    InvalidAddress,
};

// 组合错误集合
const IOError = FileError || NetworkError;

fn simulateFileOperation(filename: []const u8) FileError!void {
    if (std.mem.eql(u8, filename, "readonly.txt")) {
        return FileError.PermissionDenied;
    }
    if (std.mem.eql(u8, filename, "missing.txt")) {
        return FileError.NotFound;
    }
    // 模拟成功
    std.debug.print("文件操作成功: {s}\n", .{filename});
}

fn simulateNetworkOperation(address: []const u8) NetworkError!void {
    if (std.mem.eql(u8, address, "invalid")) {
        return NetworkError.InvalidAddress;
    }
    if (std.mem.eql(u8, address, "timeout")) {
        return NetworkError.Timeout;
    }
    std.debug.print("网络操作成功: {s}\n", .{address});
}

pub fn main() void {
    // 测试文件操作
    const files = [_][]const u8{ "normal.txt", "readonly.txt", "missing.txt" };
    for (files) |file| {
        simulateFileOperation(file) catch |err| {
            std.debug.print("文件操作失败: {s} -> {}\n", .{ file, err });
        };
    }
    
    // 测试网络操作
    const addresses = [_][]const u8{ "192.168.1.1", "invalid", "timeout" };
    for (addresses) |addr| {
        simulateNetworkOperation(addr) catch |err| {
            std.debug.print("网络操作失败: {s} -> {}\n", .{ addr, err });
        };
    }
}

推断错误集合

Zig 可以自动推断函数可能返回的错误:

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

const Error1 = error{A, B};
const Error2 = error{B, C};

fn function1() Error1!void {
    return Error1.A;
}

fn function2() Error2!void {
    return Error2.C;
}

// 错误集合会被自动推断为 Error1 || Error2
fn combinedFunction(choice: u8) !void {
    switch (choice) {
        1 => try function1(),
        2 => try function2(),
        else => {},
    }
}

pub fn main() void {
    const choices = [_]u8{ 0, 1, 2 };
    
    for (choices) |choice| {
        combinedFunction(choice) catch |err| {
            std.debug.print("选择 {} 导致错误: {}\n", .{ choice, err });
        };
    }
}

anyerror 类型

使用 anyerror

anyerror 可以表示任何错误类型:

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

const CustomError = error{
    CustomFailure,
};

fn maybeError(should_error: bool) anyerror!i32 {
    if (should_error) {
        return CustomError.CustomFailure;
    }
    return 42;
}

fn handleAnyError(result: anyerror!i32) void {
    if (result) |value| {
        std.debug.print("成功获得值: {}\n", .{value});
    } else |err| {
        std.debug.print("遇到错误: {}\n", .{err});
    }
}

pub fn main() void {
    handleAnyError(maybeError(false));
    handleAnyError(maybeError(true));
}

错误返回跟踪

错误返回跟踪

在调试模式下,Zig 可以跟踪错误的传播路径:

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

const ChainError = error{
    Level1Error,
    Level2Error,
    Level3Error,
};

fn level3Function() ChainError!void {
    return ChainError.Level3Error;
}

fn level2Function() ChainError!void {
    try level3Function();
}

fn level1Function() ChainError!void {
    try level2Function();
}

pub fn main() void {
    level1Function() catch |err| {
        std.debug.print("捕获到错误: {}\n", .{err});
        
        // 在调试模式下,可以获取错误跟踪信息
        if (@errorReturnTrace()) |trace| {
            std.debug.print("错误跟踪信息可用\n", .{});
        }
    };
}

实际应用示例

文件读取示例

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

const FileReadError = error{
    FileNotFound,
    PermissionDenied,
    ReadError,
    OutOfMemory,
};

fn readFileContent(allocator: std.mem.Allocator, filename: []const u8) FileReadError![]u8 {
    // 模拟文件读取
    if (std.mem.eql(u8, filename, "missing.txt")) {
        return FileReadError.FileNotFound;
    }
    
    if (std.mem.eql(u8, filename, "protected.txt")) {
        return FileReadError.PermissionDenied;
    }
    
    // 模拟成功读取
    const content = try allocator.dupe(u8, "文件内容示例");
    return content;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const filenames = [_][]const u8{ "normal.txt", "missing.txt", "protected.txt" };
    
    for (filenames) |filename| {
        if (readFileContent(allocator, filename)) |content| {
            defer allocator.free(content);
            std.debug.print("读取文件 '{s}' 成功: {s}\n", .{ filename, content });
        } else |err| {
            std.debug.print("读取文件 '{s}' 失败: {}\n", .{ filename, err });
        }
    }
}

JSON 解析示例

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

const JsonError = error{
    InvalidFormat,
    MissingField,
    TypeMismatch,
};

const User = struct {
    name: []const u8,
    age: u32,
    email: []const u8,
};

fn parseUser(json_str: []const u8) JsonError!User {
    // 简化的 JSON 解析示例
    if (std.mem.indexOf(u8, json_str, "name") == null) {
        return JsonError.MissingField;
    }
    
    if (std.mem.indexOf(u8, json_str, "age") == null) {
        return JsonError.MissingField;
    }
    
    if (json_str.len < 10) {
        return JsonError.InvalidFormat;
    }
    
    // 模拟解析成功
    return User{
        .name = "张三",
        .age = 25,
        .email = "zhangsan@example.com",
    };
}

pub fn main() void {
    const json_samples = [_][]const u8{
        "{\"name\":\"张三\",\"age\":25,\"email\":\"zhangsan@example.com\"}",
        "{\"name\":\"李四\"}",
        "invalid",
        "{}",
    };
    
    for (json_samples, 0..) |json, i| {
        std.debug.print("解析 JSON 样本 {}:\n", .{i + 1});
        
        if (parseUser(json)) |user| {
            std.debug.print("  成功: 姓名={s}, 年龄={}, 邮箱={s}\n", 
                          .{ user.name, user.age, user.email });
        } else |err| {
            std.debug.print("  失败: {}\n", .{err});
        }
    }
}

错误处理的最佳实践

1. 明确的错误类型

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

// ✅ 好的做法:明确的错误类型
const DatabaseError = error{
    ConnectionFailed,
    QueryTimeout,
    InvalidQuery,
    RecordNotFound,
};

fn queryDatabase(query: []const u8) DatabaseError![]const u8 {
    if (std.mem.eql(u8, query, "")) {
        return DatabaseError.InvalidQuery;
    }
    
    if (std.mem.eql(u8, query, "SELECT * FROM missing_table")) {
        return DatabaseError.RecordNotFound;
    }
    
    return "查询结果";
}

pub fn main() void {
    const queries = [_][]const u8{ "SELECT * FROM users", "", "SELECT * FROM missing_table" };
    
    for (queries) |query| {
        if (queryDatabase(query)) |result| {
            std.debug.print("查询成功: {s}\n", .{result});
        } else |err| {
            // 可以针对不同错误类型进行不同处理
            switch (err) {
                DatabaseError.ConnectionFailed => std.debug.print("数据库连接失败\n", .{}),
                DatabaseError.QueryTimeout => std.debug.print("查询超时\n", .{}),
                DatabaseError.InvalidQuery => std.debug.print("无效查询\n", .{}),
                DatabaseError.RecordNotFound => std.debug.print("记录未找到\n", .{}),
            }
        }
    }
}

2. 适当的错误传播

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

const ValidationError = error{
    InvalidEmail,
    InvalidAge,
    InvalidName,
};

fn validateEmail(email: []const u8) ValidationError!void {
    if (std.mem.indexOf(u8, email, "@") == null) {
        return ValidationError.InvalidEmail;
    }
}

fn validateAge(age: u32) ValidationError!void {
    if (age < 18 or age > 120) {
        return ValidationError.InvalidAge;
    }
}

fn validateName(name: []const u8) ValidationError!void {
    if (name.len == 0 or name.len > 50) {
        return ValidationError.InvalidName;
    }
}

// 组合验证函数
fn validateUser(name: []const u8, age: u32, email: []const u8) ValidationError!void {
    try validateName(name);
    try validateAge(age);
    try validateEmail(email);
}

pub fn main() void {
    const users = [_]struct { name: []const u8, age: u32, email: []const u8 }{
        .{ .name = "张三", .age = 25, .email = "zhangsan@example.com" },
        .{ .name = "", .age = 25, .email = "invalid-email" },
        .{ .name = "李四", .age = 200, .email = "lisi@example.com" },
    };
    
    for (users, 0..) |user, i| {
        std.debug.print("验证用户 {}:\n", .{i + 1});
        
        validateUser(user.name, user.age, user.email) catch |err| {
            switch (err) {
                ValidationError.InvalidName => std.debug.print("  姓名无效\n", .{}),
                ValidationError.InvalidAge => std.debug.print("  年龄无效\n", .{}),
                ValidationError.InvalidEmail => std.debug.print("  邮箱无效\n", .{}),
            }
            continue;
        };
        
        std.debug.print("  用户验证通过\n", .{});
    }
}

3. 资源清理与错误处理

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

const ResourceError = error{
    AllocationFailed,
    InitializationFailed,
    ProcessingFailed,
};

fn processWithResource(allocator: std.mem.Allocator, size: usize) ResourceError!void {
    // 分配资源
    const buffer = allocator.alloc(u8, size) catch {
        return ResourceError.AllocationFailed;
    };
    defer allocator.free(buffer);
    
    // 初始化资源
    if (size > 1000) {
        return ResourceError.InitializationFailed;
    }
    
    // 处理数据
    if (size == 0) {
        return ResourceError.ProcessingFailed;
    }
    
    std.debug.print("成功处理 {} 字节的数据\n", .{size});
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const sizes = [_]usize{ 100, 0, 1500 };
    
    for (sizes) |size| {
        processWithResource(allocator, size) catch |err| {
            std.debug.print("处理大小 {} 时出错: {}\n", .{ size, err });
        };
    }
}

总结

本章详细介绍了 Zig 的错误处理系统:

  • ✅ 错误的基本概念和错误联合类型
  • ✅ 多种错误处理方式:ifcatchtry
  • ✅ 错误集合的定义和组合
  • anyerror 类型的使用
  • ✅ 错误返回跟踪
  • ✅ 实际应用示例
  • ✅ 错误处理的最佳实践

Zig 的错误处理系统强制开发者明确处理可能出现的错误,这大大提高了程序的可靠性和安全性。在下一章中,我们将学习 Zig 的内存管理。

本站内容仅供学习和研究使用。