Go 1.13
Go 1.13 的发布为错误处理进带来了全新功能 Error wrapping。
具体细节可以参考相关提案 Proposal: Go 2 Error Inspection 以及 issues/29934 讨论。
Go 1.13 为 errors 和 fmt 标准库包引入了新功能:
fmt.Errorf支持使用%w动词包装错误。- 新增
errors.Unwrap函数为错误解包以获取根因。 - 新增
errors.Is函数取代==做等值比较。 - 新增
errors.As函数取代Type Assertion做类型断言。
我们来一一讲解。
fmt.Errorf
fmt.Errorf 新增的 %w 动词功能对标 pkg/errors 包中的 errors.Wrap 函数,用法如下:
package main
import (
"errors"
"fmt"
)
func Foo() error {
return errors.New("foo error")
}
func Bar() error {
err := Foo()
if err != nil {
return fmt.Errorf("bar: %w", err)
}
return nil
}
func main() {
err := Bar()
if err != nil {
fmt.Printf("err: %s\n", err)
}
}
执行示例代码,得到输出如下:
$ go run main.go
err: bar: foo error
errors.Unwrap
errors.Unwrap 函数对标 pkg/errors 包中的 errors.Cause 函数,用法如下:
func Foo() error {
return io.EOF
}
func Bar() error {
err := Foo()
if err != nil {
return fmt.Errorf("bar: %w", err)
}
return nil
}
func main() {
err := Bar()
if err != nil {
if errors.Unwrap(err) == io.EOF {
fmt.Println("EOF err")
return
}
fmt.Printf("err: %+v\n", err)
}
return
}
执行示例代码,得到输出如下:
$ go run main.go
EOF err
errors.Is
errors.Is 函数可以取代 == 做等值比较,用法如下:
func Foo() error {
return io.EOF
}
func Bar() error {
err := Foo()
if err != nil {
return fmt.Errorf("bar: %w", err)
}
return nil
}
func main() {
err := Bar()
if err != nil {
// if err == io.EOF {
if errors.Is(err, io.EOF) {
fmt.Println("EOF err")
return
}
fmt.Printf("err: %+v\n", err)
}
return
}
执行示例代码,得到输出如下:
$ go run main.go
EOF err
由于我们使用了 fmt.Errorf("bar: %w", err) 对初始错误进行了包装,所以在 main 函数中不能直接使用 if err == io.EOF 来对 Sentinel error 进行相等性判断。
除了可以使用 if errors.Unwrap(err) == io.EOF 这种方式,我们还可以使用 errors.Is(err, io.EOF) 方式来取代 ==,执行结果相同。
errors.As
errors.As 函数可以取代 Type Assertion 做类型断言,用法如下:
type MyError struct {
msg string
err error
}
func (e *MyError) Error() string {
return e.msg + ": " + e.err.Error()
}
func Foo() error {
return &MyError{
msg: "foo",
err: io.EOF,
}
}
func Bar() error {
err := Foo()
if err != nil {
return fmt.Errorf("bar: %w", err)
}
return nil
}
func main() {
err := Bar()
if err != nil {
var e *MyError
if errors.As(err, &e) {
fmt.Printf("EOF err: %s\n", e)
return
}
fmt.Printf("err: %+v\n", err)
}
return
}
执行示例代码,得到输出如下:
$ go run main.go
EOF err: foo: EOF
Go 1.20
Go 1.20 新增了 errors.Join 函数返回包装后的错误列表。
用法如下:
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("err1")
err2 := errors.New("err2")
err := errors.Join(err1, err2)
fmt.Println("---------")
fmt.Println(err)
fmt.Println("---------")
if errors.Is(err, err1) {
fmt.Println("err is err1")
}
if errors.Is(err, err2) {
fmt.Println("err is err2")
}
}
执行示例代码,得到输出如下:
$ go run main.go
---------
err1
err2
---------
err is err1
err is err2
可以发现通过 errors.Join(err1, err2) 得到的 err 对象打印结果中会在多个错误之间增加换行。并且 errors.Is 函数也做了升级,能够处理这种情况,对 err1 或 err2 的相等性比较结果都为 true。
Go 1.23
Go 1.23 对 errors.Is 函数做了一点小优化。
这是 Go 1.23 中的 errors.Is 函数:
func Is(err, target error) bool {
if err == nil || target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
这是 Go 1.22.8 中的 errors.Is 函数:
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
可以发现 Go 1.23 中 errors.Is 函数多了一个 if err == nil 逻辑的判断。不过由于这个改动比较小,不会对现有代码造成任何影响,这一改变并没有出现在 Go 1.23 Release Notes 中。
仅此而已。
Go 2
Go 1.13 版本之前的错误处理是过去,Go 1.13 版本错误处理是现在,Go 2 版本错误处理是未来。
由于社区中对 Go 错误处理的吐槽声一直不断,Go 也在努力改变这一现状,虽然进展缓慢。
Go 1.13 的出现已经是最大的诚意了 :)。
很多人依然不满足于现状,将错误处理希望寄托于 Go 2。不过我个人持悲观态度,基于 Go 泛型的演进过程,我认为几年内 Go 错误处理都不会有较大改变。
不过 Go 2 的蓝图的确已经在勾勒中了,感兴趣的读者可以进入 Go 2 Draft Designs 查看,里面对 Error handling 以及 Error values 都罗列了几个链接供读者参阅。
期待 Go 2 的错误处理能够更上一层楼。
总结
Go 1.13 虽然新增了几个方法,用来辅助错误处理,但是依然不能记录堆栈信息。
所以,即使有 Go 1.13 的存在,现在也依然推荐使用 pkg/errors 来处理错误。
Go 2 承担了 Go 未来错误处理的重任。
本文示例源码我都放在了 GitHub 中,欢迎点击查看。
希望此文能对你有所启发。
联系我
- 公众号:Go编程世界
- 微信:jianghushinian
- 邮箱:jianghushinian007@outlook.com
- 博客:jianghushinian.cn