Go 1.13 中errors包有了一些变化,这些变化是为了更好地支持Go的错误处理提案。Go 1.20中也增加了一个新方法,这个新方法可以代替第三方的库处理多个error,这篇文章将介绍这些变化。
因为原来的Go的errors中的内容非常的简单,可能会导致大家轻视这个包,对于新的变化不是那么的关注。让我们一一介绍这些新的方法。
Unwrap
如果一个err实现了Unwrap
函数,那么errors.Unwrap
会返回这个err的unwrap
方法的结果,否则返回nil。
一般标准的error都没有实现Unwrap
方法,比如io.EOF
, 但是也有一小部分的error实现了Unwrap
方法,比如os.PathError
,os.LinkError
、os.SyscallError
、net.OpError
、net.DNSConfigError
等等。
比如下面的代码:
|
|
第一行因为io.EOF
没有Unwrap
方法,所以输出nil。
net.Dial失败返回的err是*net.OpError
,它实现了Unwrap
方法,返回更底层的*net.DNSError
,所以第二行输出为lookup invalid.address: no such host
。
最常用的,我们使用fmt.Errorf
+ %w
包装一个error,比如下面的代码:
|
|
这段代码逐层进行了包装,最后的e4
包含了所有的error,我们可以通过errors.Unwrap
逐层进行解包,直到最底层的error。
fmt.Errorf可以1一次包装多个error,比如上面的e2
,它包含了e1
和io.ErrClosedPipe
两个error。
我们常常在多层调用的时候,把最底层的error逐层包装传递上去,这个时候我们可以使用fmt.Errorf
+ %w
包装error。
在最高层处理error的时候,再逐层Unwrap
解开error,逐层处理。
Is
Is
函数检查error的树中是否包含指定的目标error。
啥是error的树? 一个error的数包括它本身,以及通过Unwrap
方法逐层解开的error。
error的Unwrap
方法的返回值,可能是单个error,也可能是是多个error,在返回多个error的时候,会采用深度优先的方式进行遍历检查,寻找目标error。
怎么才算找到目标error呢?一种情况就是此err就是目标error,这没有什么好说的,第二种就是此err实现了Is(err)
方法,把目标err扔进Is
方法返回true。
所以从功能上看Is
函数其实叫做Has
函数更贴切些。
下面是一个例子:
|
|
As
Is
是遍历error的数,检查是否包含目标error。As
是遍历error的数,检查每一个error,看看是否可以把从error赋值给目标变量,如果是,则返回true,并且目标变量已赋值,否则返回false。
下面这个例子,我们可以看到As
的用法:
|
|
如果os.Open返回的error的树中包含*fs.PathError
,那么errors.As
会把这个error赋值给pathError
变量,并且返回true,否则返回false。
我们这个例子正好制造的就是文件不存在的error,所以它会输出:failed at path: non-existing
经常常犯的一个错误就是我们使用一个error
变量作为As
的第二个参数。下面这个例子tmp就是error接口类型,所以origin可以直接赋值给tmp,所以errors.As
返回true,并且tmp的值就是origin的值。
|
|
As
使用起来总是那么别别扭扭,每次总得声明一个变量,然后把这个变量传递给As
函数,在Go支持泛型之后,As
应该可以简化成如下的方式:
|
|
但是,Go不会修改这个导致不兼容的API,所以我们只能继续保留As
函数,增加一个新的函数是一个可行的方法,无论它叫做IsA
、AsOf
还是AsTarget
或者其他。
如果你已经掌握了Go的泛型,你可以自己实现一个As
函数,比如下面的代码:
|
|
写段测试代码,我们可以看到它的效果:
|
|
大家在#51945讨论了一段时间,又是无疾而终了。
Join
在我们的项目中,有时候需要处理多个error,比如下面的代码:
|
|
这段代码中,我们需要处理三个error,如果有一个error不为nil,那么我们就返回errs。
当然,为了处理多个errors情况,先前,有很多的第三方库可以供我们使用,比如
- go.uber.org/multierr
- github.com/hashicorp/go-multierror
- github.com/cockroachdb/errors
但是现在,你不用再造轮子或者使用第三方库了,因为Go 1.20中增加了errors.Join
函数,它可以把多个error合并成一个error,比如下面的代码:
|
|
你可以使用Is
判断是否包含某个error,或者使用As
提取出目标error。