Go vs.Zig 中的错误处理

重新定义错误处理:Go和Zig的“错误即价值”哲学

image.png|300

注:本文核心内容由大语言模型生成,辅以人工事实核查与结构调整。

在现代编程语言,尤其是系统编程语言中,有效的错误处理至关重要。Go 和 Zig 这两种越来越受关注的语言在错误处理方面共享一个基本理念:“错误是值(Errors are values)”。这一原则意味着错误不是像异常那样的“二等公民”,而是可以传递、检查、编程的普通值。两种语言都强调简洁性和“无隐藏控制流”的设计哲学,目的是让错误的传播和处理变得显式。然而,尽管它们有着相同的出发点,具体实现却大不相同,各自形成了独特的处理方式与权衡。


Go 的错误处理

Go 的错误处理机制以其直接明了而著称。在 Go 中,错误本质上是任何实现了内置 error 接口的类型。这个接口只定义了一个方法:Error() string。这种简单而强大的约定允许开发者创建自定义错误类型,可以包含各种数据,并提供丰富的上下文信息。例如,一个文件操作可能返回 os.PathError,它包含操作名(Op)、文件路径(Path)以及底层系统错误等字段。标准库中的 errors 包还提供了实用函数,如 errors.Is 用于判断错误是否匹配某个目标错误,errors.As 则用于检查和提取错误链中的特定错误类型。

Go 中的函数通常通过返回多个值来指示是否发生错误,错误值通常是最后一个返回值(例如:func Open(name string) (*File, error))。调用者需要显式地检查错误是否为 nil,这就是 Go 中标志性的 if err != nil 语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 返回错误的 Go 函数
func Open(name string) (*File, error)

// 在调用处检查错误
f, err := os.Open("main.zig")
if err != nil {
// 处理错误
}

// 一行写法的错误检查
if err := f.Close(); err != nil {
log.Fatal(err)
}

// 自定义错误类型
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
func New(text string) error {
return &errorString{text}
}

// 使用 errors.Is 检查错误类型
if errors.Is(err, fs.ErrNotExist) {
// 处理错误
}

虽然这种显式的错误检查让控制流更加清晰,并确保错误不会被轻易忽略,但也可能导致代码冗长,其中 if err != nil 的代码块可能占据代码库的很大一部分。有批评者甚至称这种写法为“if err 那些半天没干正事的废话”。然而,Go 的理念强调“错误是值”,意味着错误是可以被编程处理的。这使得一些设计模式可以用来抽象掉重复的错误检查逻辑。

例如,bufio.ScannerScan() 方法返回一个布尔值用于控制流程,而错误则在扫描结束后通过 Err() 方法单独报告;再比如 errWriter 模式,它会记录首次出现的错误,并让后续的写操作变成空操作(no-op),直到错误被处理为止。这种做法通过集中管理错误检查逻辑来简化代码结构。

Zig 错误处理

Zig 是一门较新的编程语言,它也将错误视为值,但通过一种专门的枚举机制来实现,这种枚举可以被隐式地创建。错误通过 error sets(错误集) 来定义,类似于专门用于错误值的枚举。可能失败的函数通过返回 错误联合类型(error union) 来表达这一点,其语法为 !Terror{…}!T,表示该函数可能返回类型为 T 的值,也可能返回一个错误。

与 Go 的关键区别在于:Zig 编译器强制要求处理所有可能的错误;你不能像在 Go 中那样使用空白标识符 _ 忽略返回的错误。这种编译时检查增强了语言的可靠性。

Zig 提供了一些强大的语法糖来简化错误处理:

  • try 关键字:用于将错误向上传递,这是一个非常简洁的机制。使用 try 可以代替 Go 中三四行的 if err != nil { return err } 代码。

  • catch 关键字:用于捕获并处理错误,而不是继续传递错误。

    • 可以结合代码块使用(如 catch |err| { … })来执行自定义的错误处理逻辑,比如记录日志。

    • 可以在错误发生时提供一个备用返回值(如 catch fallbackValue),返回一个默认的成功值。

    • 可以配合命名代码块,在返回备用值前进行更复杂的处理。

  • if-else-switch 构造:允许根据特定的错误类型进行精细的分支处理。

  • catch unreachable:用于那些不应当出错的场景(例如测试、构建脚本或临时工具)。如果发生错误,程序将 panic。

示例:Zig 返回错误联合的函数 !usize

1
2
3
4
5
6
7
fn get_args_count(allocator: std.mem.Allocator) !usize {
const args = try std.process.argsAlloc(allocator); // 使用 try 传递错误
if (args.len < 1) {
return error.EmptyArgs;
}
return args.len;
}

主函数中的几种错误处理方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub fn main() void {
// 使用代码块捕获错误
const args_count = get_args_count(allocator) catch |err| {
std.debug.print("invalid input: {}\n", .{err});
return;
};

// 使用备用值捕获错误
const args_count_fallback = get_args_count(allocator) catch 0;

// 使用 if-else-switch 精确处理错误类型
if (get_args_count(allocator)) |args_count_val| {
std.debug.print("got {d} args\n", .{args_count_val});
} else |err| switch (err) {
error.EmptyArgs => {
std.debug.print("invalid input: no args\n", .{});
},
else => {
std.debug.print("unexpected error: {}\n", .{err});
},
}

// 使用 catch unreachable 断言永远不会出错
const args_count_unreachable = get_args_count(allocator) catch unreachable;
std.debug.print("got {d} args\n", .{args_count_unreachable});
}

Zig 中定义错误集

1
const FileError = error { NotFound, PermissionDenied, InvalidPath };

虽然 Zig 中的错误本质上是枚举值,本身不携带丰富的上下文信息或行为,但它们在运行时仍然保留可调试的错误追踪(errorReturnTrace)。重要的是,如果不显式使用该追踪信息,它在运行时是零开销的。


比较与权衡

Go 和 Zig 都坚持“错误是值”的哲学,避免了异常机制带来的隐式控制流。然而,它们的实现方式反映了不同的设计取舍:

  • Go 的方法更加直接,尽管冗长笨拙,但它通过接口系统允许构建丰富的上下文错误。你可以定义自定义错误类型,携带详细信息,进行深入的“错误编程”。显式的 if err != nil 检查让每个潜在的失败点都一目了然。

  • Zig 则提供了更加简洁、功能丰富且强大的语法糖(如 trycatch)以及编译器强制处理的机制。这极大地减少了错误传播时的样板代码。然而,它的基于枚举的错误类型天然缺乏上下文信息。虽然可以通过 tagged union 等方式附加信息,但这在 Zig 中并不如 Go 的接口系统那样常见和习惯。

简而言之:

  • Go 优先考虑灵活性和详细的错误信息,但代价是冗余的代码;

  • Zig 优先考虑简洁性和编译期的错误保障,但可能牺牲了部分上下文的表达能力。

两者各有优劣,体现了不同的语言设计哲学。

参考

  • “Comparing error handling in Zig and Go | by Alex Pliutau - ITNEXT”
  • “Error Handling in Zig: A Fresh Approach to Reliability - DEV Community”
  • “Error handling in Zig vs Go : r/programming - Reddit”
  • “Errors are values - The Go Programming Language”

更多内容

最近文章:

随机文章:


更多该系列文章,参考medium链接:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

English post: https://programmerscareer.com/go-zig-error/
作者:微信公众号,Medium,LinkedIn,Twitter
发表日期:原文在 2025-08-02 21:57 时创作于 https://programmerscareer.com/zh-cn/go-zig-error/
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

Vibe规范:规范先行的AI开发 Go 1.24 Map 改进:Swiss Tables 性能提升

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×