https://www.zhihu.com/question/36444352
Go 的错误处理是“积类型”,而 Rust 是“和类型”。这是最本质的区别。
在1.20之前的版本,fmt.Errorf后面只能格式化一个%w,两个的话会不符合预期,如下:
1 | package main |
err1 is strconv.Atoi: parsing "1a": invalid syntax; err2 is %!w(*errors.errorString=&{new error})
用 Go 1.20及以上版本:
err1 is strconv.Atoi: parsing "1a": invalid syntax; err2 is new error
相关
术语从 “错误链” 修改为 “错误树”。配套方法 errors.Is、errors.As、fmt.Errorf 都进行了改造。
对应如下:
- errors.Is:如果能够匹配上任何错误,则返回 true。
- errors.As:返回第一个匹配的错误。
- fmt.Errorf:将会把多个错误封装在用户定义的布局中。
新增了一个 errors.Join
Go 1.20新特性前瞻-支持wrap multiple errors
20240818
我将按照您的要求整理内容,保持原有信息不遗漏,并使其更加合理通顺。以下是整理后的内容:
Go 语言 errors 包的改进历史和用法(上)
Go 语言 errors 包的发展历程:
2012年:Go 1.0 发布,error 已经是接口实现。
2019年:Go 1.13 版本发布,新增功能:
- errors.Is 函数
- errors.As 函数
- 解包函数
- 装包函数
2023年:新增 errors.Join 函数,作用是将多个错误合成一个错误。新增解包接口返回 slice 类型。
Go 1.13 之前的错误处理:
以 HTTP Get 函数返回中获取 syscall.ECONNREFUSED 错误为例:
1 | if err != nil { |
这种方法的局限性:
- 需要了解错误的完整路径
- 类似于目录查找,需要知道绝对路径
- 心智成本高
Go 1.13 版本的改进:
使用 errors.Is 函数:
1 | if errors.Is(err, syscall.ECONNREFUSED) { |
改进点:
- 只需知道具体错误的名字
- 可以直接判断是否存在这个错误
- 心智成本大幅降低
错误分类的改进:
Go 1.13 之前的做法:
1 | err := fmt.Errorf("调用标准库发生错误: %v", stdLibErr) |
缺点:
- 丢失结构化信息
- 只能通过字符串方式分类
- 对上游依赖脆弱,容错性差
Go 1.13 之后的做法:
1 | var ErrNotFound = errors.New("not found") |
优点:
- 错误包装不会丢失结构化信息
- 形成错误链表
实际应用示例:
定义错误结构:
1 | type ErrMsg struct { |
使用示例:
1 | func getUserInfo(id string) (string, error) { |
Go 1.13 带来的主要改进:
- errors.Is:在错误链中查找特定错误
- errors.As:在错误链中查找特定类型的错误并提取
- fmt.Errorf 的 %w:对错误进行包装
- errors.Unwrap:对错误进行解包(每次只能解包一次,使用较少)
错误合并:
- 使用 fmt.Errorf:
1 | err3 := fmt.Errorf("错误3: %w %w", err1, err2) |
- 使用 errors.Join(Go 1.20 新增):
1 | err3 := errors.Join(err1, err2) |
性能对比:
- errors.Join:每次操作约 55.09 纳秒
- fmt.Errorf:每次操作约 210 纳秒
结论:errors.Join 性能更好,只占用 fmt.Errorf 约 25% 的时间。推荐在 Go 1.20 及以上版本使用 errors.Join 实现错误合并。
总结:
- Go 1.13 版本提供了易于使用的错误包装和解包函数,将错误处理变成对错误链的处理。
- Go 1.20 版本的 errors.Join 增强了错误合并功能,主要是性能优化。
- errors.Join 相比 fmt.Errorf 在实现不定长 error 合并时更加方便。
下一步:分析 errors 包的源代码实现。
原文链接: https://dashen.tech/2020/01/14/Go-1-20中fmt-Errorf的更新/
版权声明: 转载请注明出处.