rors 库增加了 Is/As/Unwrap三个函数,这将用于支持错误的再次包装和识别处理,为 Go 2 中新的错误处理改进提前做准备。
1.13
支持了 error
包裹(wrapping):
An error e can wrap another error w by providing an Unwrap method that returns w. Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.
为了支持 wrapping,fmt.Errorf
增加了 %w
的格式,并且在 error
包增加了三个函数:errors.Unwrap
,errors.Is
,errors.As
。
fmt.Errorf
使用 fmt.Errorf
加上 %w
格式符来生成一个嵌套的 error,它并没有像 pkg/errors
那样使用一个 Wrap 函数来嵌套 error,非常简洁。
Unwrap
func Unwrap(err error) error
将嵌套的 error 解析出来,多层嵌套需要调用 Unwrap
函数多次,才能获取最里层的 error。
源码如下:
func Unwrap(err error) error {
// 判断是否实现了 Unwrap 方法
u, ok := err.(interface {
Unwrap() error
})
// 如果不是,返回 nil
if !ok {
return nil
}
// 调用 Unwrap 方法返回被嵌套的 error
return u.Unwrap()
}
对 err 进行断言,看它是否实现了 Unwrap 方法,如果是,调用它的 Unwrap 方法。否则,返回 nil。
Is
func Is(err, target error) bool
判断 err 是否和 target 是同一类型,或者 err 嵌套的 error 有没有和 target 是同一类型的,如果是,则返回 true。
源码如下:
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
// 无限循环,比较 err 以及嵌套的 error
for {
if isComparable && err == target {
return true
}
// 调用 error 的 Is 方法,这里可以自定义实现
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// 返回被嵌套的下一层的 error
if err = Unwrap(err); err == nil {
return false
}
}
}
通过一个无限循环,使用 Unwrap
不断地将 err 里层嵌套的 error 解开,再看被解开的 error 是否实现了 Is 方法,并且调用它的 Is 方法,当两者都返回 true 的时候,整个函数返回 true。
As
func As(err error, target interface{}) bool
从 err 错误链里找到和 target 相等的并且设置 target 所指向的变量。
源码如下:
func As(err error, target interface{}) bool {
// target 不能为 nil
if target == nil {
panic("errors: target cannot be nil")
}
val := reflectlite.ValueOf(target)
typ := val.Type()
// target 必须是一个非空指针
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
// 保证 target 是一个接口类型或者实现了 Error 接口
if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
targetType := typ.Elem()
for err != nil {
// 使用反射判断是否可被赋值,如果可以就赋值并且返回true
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
// 调用 error 自定义的 As 方法,实现自己的类型断言代码
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
// 不断地 Unwrap,一层层的获取嵌套的 error
err = Unwrap(err)
}
return false
}
返回 true 的条件是错误链里的 err 能被赋值到 target 所指向的变量;或者 err 实现的 As(interface{}) bool
方法返回 true。
前者,会将 err 赋给 target 所指向的变量;后者,由 As 函数提供这个功能。
如果 target 不是一个指向“实现了 error 接口的类型或者其它接口类型”的非空的指针的时候,函数会 panic。
这一部分的内容,飞雪无情大佬的文章【飞雪无情 分析 1.13 错误】写得比较好,推荐阅读。
总结
Go 语言使用 error 和 panic 处理错误和异常是一个非常好的做法,比较清晰。至于是使用 error 还是 panic,看具体的业务场景。
当然,Go 中的 error 过于简单,以至于无法记录太多的上下文信息,对于错误包裹也没有比较好的办法。当然,这些可以通过第三方库来解决。官方也在新发布的 go 1.13 中对这一块作出了改进,相信在 Go 2 里会有更进一步的优化。
本文还列举了一些处理 error 的示例,例如不要两次处理一个错误,判断错误的行为而不是类型等等。
参考资料里列举了很多错误处理相关的示例,这篇文章作为一个引子。
参考资料
【Go 2 错误提案】https://go.googlesource.com/proposal/+/master/design/29934-error-values.md
【check & handle】https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md
【错误讨论的 issue】https://github.com/golang/go/issues/29934
【error value 的 FAQ】https://github.com/golang/go/wiki/Error