文章目录
一、默认的 error 实现
首先,go 定义了 error interface,并提供了默认的实现
type error interface {
Error() string
}
// 默认的实现
typr errorString struct {
s string
}
func (e *errorString) Error() String {
return e.s
}
二、自定义 error 实现
那么,如果想自定义 error 类型,则可通过如下两种方法:
func main() {
errA := errors.New("new error a");
fmt.Println(errA)
errB := fmt.Errorf("new error %s", "b");
fmt.Println(errB)
}
// go run main.go
new error a
new error b
2.1 通过 fmt.Errorf(%w) 创建嵌套 error
wrapError 是官方定义的,嵌套的 error,其也实现了 error interface,并声明了一个 Unwrap() 方法来拆包装。
定义如下:
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
使用方法如下:
var baseErr = errors.New("baseErr");
func main() {
err1 := fmt.Errorf("wrap base: %w", baseErr);
fmt.Println(err1)
err2 := fmt.Errorf("wrap err1: %w", err1);
fmt.Println(err2)
}
// go run
wrap base: baseErr
wrap err1: wrap base: baseErr
2.1.1 fmt.Errorf() 实现
为什么 fmt.Errorf
用了占位符 %w
之后创建的就是 wrapError 类型,而用了 fmt.Errorf
但只是选择其他占位符如上述示例中的 %s
创建的就是 errorString 类型?
可以简单看一下 fmt.Errorf
方法的源码:
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand.
// If there is more than one %w verb, the returned error will implement an
// Unwrap method returning a []error containing all the %w operands in the
// order they appear in the arguments.
// It is invalid to supply the %w verb with an operand that does not implement
// the error interface. The %w verb is otherwise a synonym for %v.
func Errorf(format string, a ...any) error {
// 先申请一个 pp struct, 其中有 buf buffer、wrapErrs bool 和 wrappedErrs []int 等属性
/*
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
buf buffer
...
// wrapErrs is set when the format string may contain a %w verb.
wrapErrs bool
// wrappedErrs records the targets of the %w verb.
wrappedErrs []int
}
*/
p := newPrinter()
// 手动设置 wrapErrs, 表示 format 可能含有 %w 谓词
p.wrapErrs = true
// 遍历 format 字符串的每个字符
// 如果其中含有 %w 则通过 p.wrappedErrs = append(p.wrappedErrs, argNum) 追加 %w 的下标位置到 p.wrappedErrs 数组中
p.doPrintf(format, a)
s := string(p.buf) // s = p.buf 为输入的内容
var err error
// 判断 p.wrappedErrs 数组的长度
switch len(p.wrappedErrs) {
case 0: // 数组长度为 0,则意为没有 %w 谓词,则直接新建一个 errorString struct
err = errors.New(s)
case 1: // 数组长度为 1,则意为有一个 %w 谓词,最终会返回 wrapError struct, 它只包含一个 err error
/*
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
*/
w := &wrapError{msg: s} // 封装 wrapError struct,使其 msg 属性为 s
// a[p.wrappedErrs[0]]`表示通过`%w`动词传递给`Errorf`函数的第一个错误操作数。其中 p.wrappedErrs 是参数下标数组,p.wrappedErrs[0] 是参数下标,a 是参数数组,a[p.wrappedErrs[0]] 是第一个参数的值
// 然后,使用类型断言`(error)`将这个操作数转换为`error`类型
// 并将其赋值给`w.err`。这样,`w.err`就包含了通过`%w`传递给`Errorf`函数的第一个错误操作数。
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default: // 数组长度大于 1,则意为有大于一个 %w 谓词,最终会返回 wrapErrors struct, 它只包含数组 errs []error。总结起来,它会对参数进行排序并创建一个包含所有`%w`操作数的`[]error`切片的错误对象。
/*
type wrapErrors struct {
msg string
errs []error
}
func (e *wrapErrors) Error() string {
return e.msg
}
func (e *wrapErrors) Unwrap() []error {
return e.errs
}
*/
// 在这种情况下,返回的错误对象将实现一个`Unwrap`方法,该方法返回一个包含所有`%w`操作数的`[]error`切片,按照它们在参数中出现的顺序进行排序。
// 首先检查`p.reordered`属性是否为`true`。如果为`true`,则对`p.wrappedErrs`数组进行排序。
if p.reordered {
sort.Ints(p.wrappedErrs)
}
// 然后,创建一个切片`errs`,用于存储转换为`error`类型的参数值。
var errs []error
// 接下来,使用一个循环遍历`p.wrappedErrs`数组中的每个元素。
for i, argNum := range p.wrappedErrs {
// 在每次迭代中,检查当前元素是否与前一个元素相同。如果相同,则跳过当前迭代。
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
// 然后,检查对应参数下标的元素值是否实现了`error`接口。如果是,则将其转换为`error`类型,并将其追加到`errs`切片中。
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
// 最后,创建一个`wrapErrors`结构体,将格式化后的字符串`s`和`errs`切片作为其字段值,并将该结构体赋值给变量`err`。
err = &wrapErrors{s, errs}
}
p.free()
return err
}
// 单 err
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
// 多 errs
type wrapErrors struct {
msg string
errs []error
}
func (e *wrapErrors) Error() string {
return e.msg
}
func (e *wrapErrors) Unwrap() []error {
return e.errs
}
2.2 errors.Is()
作用:判断被包装过的error,是否包含指定错误
2.2.1 使用
package main
import (
"errors"
"fmt"
)
var baseErr = errors.New("baseErr") // 是 errorString struct 类型
var baseErr1 = errors.New("baseErr1") // 是 errorString struct 类型
func main() {
err1 := fmt.Errorf("wrap base: %w", baseErr) // 是 wrapError struct 类型
err2 := fmt.Errorf("wrap err1: %w", err1) // 是 wrapError struct 类型
// 比较 baseErr1 和 baseErr
if baseErr1 == baseErr {
println("[use = ] baseErr1 == baseErr")
} else {
// 首先,通过使用"="运算符比较baseErr1和baseErr,结果是不相等。这是因为"="运算符在比较错误变量时比较的是它们的指针地址,而不是它们的值。
// 尽管baseErr和baseErr1的文本内容相同,但它们是不同的错误变量,因此它们的指针地址也不同。因此打印的结果是 [use = ] baseErr1 != baseErr
println("[use = ] baseErr1 != baseErr")
}
if errors.Is(baseErr1, baseErr) {
println("[use is] baseErr1 is baseErr")
} else {
// 接下来,使用errors.Is()函数比较baseErr1和baseErr。这个函数的作用是检查错误链中是否存在目标错误。
// 在这种情况下,baseErr1并没有直接包含baseErr,因此返回的结果是错误的。因此打印的结果是"[use is] baseErr1 is not baseErr"。
println("[use is] baseErr1 is not baseErr")
}
// 比较 err2 和 baseErr
if err2 == baseErr {
println("[use = ] err2 == baseErr")
} else {
// 通过使用"="运算符比较err2和baseErr,结果同样是不相等。尽管err2通过fmt.Errorf()函数包装了baseErr,但它们仍然是不同的错误变量,因此指针地址不同。因此打印的结果是 "[use = ] err2 != baseErr"
println("[use = ] err2 != baseErr")
}
if errors.Is(err2, baseErr) {
// errors.Is()函数会遍历错误链,检查是否存在目标错误。在这种情况下,err2的错误链中包含了baseErr,因此返回的结果是正确的。因此打印的结果是"[use is] err2 is baseErr"。
println("[use is] err2 is baseErr")
} else {
println("[use is] err2 is not baseErr")
}
}
// go run main.go
[use = ] baseErr1 != baseErr
[use is] baseErr1 is not baseErr
[use = ] err2 != baseErr
[use is] err2 is baseErr
如果这个 err 实现了 interface{ Is(error) bool }
接口,通过接口断言,可以调用 Is
方法判断 err 是否与 target 相等。否则递归调用 Unwrap
方法拆包装,返回下一层的 error 去判断是否与 target 相等。
2.2.2 实现
// Is reports whether any error in err's tree matches target.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
// errors, Is examines err followed by a depth-first traversal of its children.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
//
// An error type might provide an Is method so it can be treated as equivalent
// to an existing error. For example, if MyError defines
//
// func (m MyError) Is(target error) bool { return target == fs.ErrExist }
//
// then Is(MyError{}, fs.ErrExist) returns true. See [syscall.Errno.Is] for
// an example in the standard library. An Is method should only shallowly
// compare err and the target and not call [Unwrap] on either.
// 用于判断错误`err`的错误树中, 是否存在与目标错误`target`匹配的错误。
func Is(err, target error) bool {
if target == nil { // 异常情况,如果 target 为 nil,则比较 err 指针是否等于 target 指针,即 判断是否 err 和 target 都是 nil
return err == target
}
// 判断`target`的类型是否是可比较的。如果是可比较的类型,则会调用`is`函数进行比较,否则直接返回`false`。
isComparable := reflectlite.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
/*
`is`函数是一个递归函数,用于在错误树中进行深度优先遍历。它首先检查`err`和`target`是否相等,如果相等且`target`是可比较的,则返回`true`。
总结起来,实现了一个递归的错误匹配算法,用于判断错误树中是否存在与目标错误匹配的错误。它会遍历错误树中的每个错误,比较错误与目标错误是否相等或是否实现了`Is(error) bool`方法,并在遍历过程中进行适当的展开
*/
func is(err, target error, targetComparable bool) bool {
for {
// 如果是可比较的,且指针相同,则为 true
if targetComparable && err == target {
return true
}
// 如果是可比较的,且 err 实现了 Is interface{} 则用 err.Is(target) 判断
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// 根据`err`的具体类型进行不同的处理:
switch x := err.(type) {
// 如果`err`实现了`Unwrap() error`方法,则调用`x.Unwrap()`获取下一级错误,并将其赋值给`err`继续进行遍历。如果下一级错误为`nil`,则表示错误树遍历结束,返回`false`。
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil { // 递归结束条件
return false
}
// 如果`err`实现了`Unwrap() []error`方法,则通过`x.Unwrap()`获取一个错误切片,并对切片中的每个错误进行递归调用`is`函数进行比较。如果其中有任何一个错误与`target`匹配,返回`true`,否则返回`false`。
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if is(err, target, targetComparable) { // is() 是递归函数
return true
}
}
return false
default: // 如果`err`不属于上述两种类型,则表示错误树遍历结束,返回`false`。
return false
}
}
}
2.3 errors.As()
作用:判断被包装过的error是否为指定类型
2.3.1 使用
package main
import (
"errors"
"fmt"
)
// 首先,定义了一个`TypicalErr`类型,并实现了`Error()`方法,使其满足`error`接口。在`main`函数中,创建了一系列通过`fmt.Errorf`函数包装的错误。
type TypicalErr struct {
e string
}
func (t TypicalErr) Error() string {
return t.e
}
func main() {
err := TypicalErr{"TypicalErr"}
err1 := fmt.Errorf("wrap err: %w", err)
err2 := fmt.Errorf("wrap err1: %w", err1)
var e TypicalErr
第二组操作中,使用`=`运算符比较`err2`和`err1`,由于它们是不同的实例,所以比较结果为`false`,打印的结果是"[use = ] err2 != err1"。然后,使用`errors.As`函数,将`err2`与`err1`进行比较,由于`err2`的类型是`*fmt.wrapError`,可以成功地将它转换为`err1`的类型,所以匹配成功,打印的结果是"[use as] err2 as err1"。接着,使用`errors.Is`函数,将`err2`与`err1`进行比较,由于它们的类型不同,所以比较结果为`false`,打印的结果是"[use is] err2 not is err1"。
第三组操作中,使用`=`运算符比较`err2`和`e`,由于它们是不同的实例,所以比较结果为`false`,打印的结果是"[use = ] err2 != e"。然后,使用`errors.As`函数,将`err2`与`e`进行比较,由于`err2`的类型是`*fmt.wrapError`,无法直接转换为`TypicalErr`类型,但它包装的错误类型是`TypicalErr`,所以匹配成功,打印的结果是"[use as] err2 as e"。接着,使用`errors.Is`函数,将`err2`与`e`进行比较,由于它们的类型不同,所以比较结果为`false`,打印的结果是"[use is] err2 not is e"。
总结起来,通过不同的方法进行错误比较和匹配,可以根据实际情况选择适合的方式来处理错误。
if err1 == e {
fmt.Println("[use = ] err1 == e")
} else {
// 由于它们是不同的实例,指针不同, 所以比较结果为`false`,打印的结果是"[use = ] err1 != e"。
fmt.Println("[use = ] err1 != e")
}
if errors.As(err1, &e) {
// 因为 err1 的错误链, 包含了 e, 所以 err1 解封装以后和 &e 的类型相同
fmt.Println("[use as] err1 as e")
} else {
fmt.Println("[use as] err1 not as e")
}
if errors.Is(err1, e) {
// 因为 err1 的错误链, 包含了 e, 所以 err1 就是 e
fmt.Println("[use is] err1 is e")
} else {
fmt.Println("[use is] err1 not is e")
}
//if errors.Is(err1, &e) {
// fmt.Println("[use is] err1 is &e")
//} else {
// // 命中此分支, 因为比较值变量和指针变量, 并没有意义
// fmt.Println("[use is] err1 not is &e")
//}
if err2 == err1 {
fmt.Println("[use = ] err2 == err1")
} else {
// 由于它们是不同的实例,指针不同, 所以比较结果为`false`,打印的结果是"[use = ] err1 != e"。
fmt.Println("[use = ] err2 != err1")
}
if errors.As(err2, &err1) {
// 因为 err2 的错误链, 包含了 err1, 所以 err2 解封装以后和 &err1 的类型相同
fmt.Println("[use as] err2 as err1")
} else {
fmt.Println("[use as] err2 not as err1")
}
if errors.Is(err2, err1) {
// 因为 err2 的错误链, 包含了 err1, 所以 err2 就是 err1
fmt.Println("[use is] err2 is err1")
} else {
fmt.Println("[use is] err2 not is err1")
}
if err2 == e {
fmt.Println("[use = ] err2 == e")
} else {
// 由于它们是不同的实例,指针不同, 所以比较结果为`false`,打印的结果是"[use = ] err1 != e"。
fmt.Println("[use = ] err2 != e")
}
if errors.As(err2, &e) {
// 因为 err2 的错误链, 包含了 e, 所以 err2 解封装以后和 &e 的类型相同
fmt.Println("[use as] err2 as e")
} else {
fmt.Println("[use as] err2 not as e")
}
if errors.Is(err2, e) {
// 因为 err2 的错误链, 包含了 e, 所以 err2 就是 e
fmt.Println("[use is] err2 is e")
} else {
fmt.Println("[use is] err2 not is e")
}
}
// go run
[use = ] err1 != e
[use as] err1 as e
[use is] err1 is e
[use = ] err2 != err1
[use as] err2 as err1
[use is] err2 is err1
[use = ] err2 != e
[use as] err2 as e
[use is] err2 is e
2.3.2 实现
// As finds the first error in err's tree that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
// errors, As examines err followed by a depth-first traversal of its children.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
/*
总结起来,`As`函数用于在错误树中查找与目标错误匹配的第一个错误,并将其设置为目标错误的值。它提供了一种灵活的错误匹配机制,可以根据错误类型提供的`As`方法实现自定义的匹配逻辑。
*/
func As(err error, target any) bool {
if err == nil { // 异常处理, err 为 nil
return false
}
if target == nil { // 异常处理, target 不能为 nil, 因为要对 target 取反射
panic("errors: target cannot be nil")
}
// 通过反射获取`target`的值和类型,并进行一些类型检查,包括检查`target`是否为非空指针、是否为接口类型或实现了`error`接口。
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() { // target 必须为指针, 必须非 nil
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem() // target 指针对应的值, 必须实现了接口, 且必须实现了 error interface{} 接口
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
// 然后,调用`as`函数进行实际的匹配过程。
return as(err, target, val, targetType)
}
// `as`函数通过遍历错误树进行深度优先遍历,类似于`errors.Is`函数。
func as(err error, target any, targetVal reflectlite.Value, targetType reflectlite.Type) bool {
for {
// 首先检查当前错误`err`是否可以被赋值给`targetType`类型,如果可以,则将错误的值设置为`target`的值,并返回`true`。
if reflectlite.TypeOf(err).AssignableTo(targetType) {
targetVal.Elem().Set(reflectlite.ValueOf(err))
return true
}
// 接着,它检查`err`是否实现了`As(interface{}) bool`方法,并调用`x.As(target)`来判断是否匹配。如果匹配成功,则返回`true`。
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
// 如果上述条件都不满足,根据`err`的具体类型进行不同的处理:
switch x := err.(type) {
// 如果`err`实现了`Unwrap() error`方法,则调用`x.Unwrap()`获取下一级错误,并将其赋值给`err`继续进行遍历。如果下一级错误为`nil`,则表示错误树遍历结束,返回`false`。
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
// 如果`err`实现了`Unwrap() []error`方法,则通过`x.Unwrap()`获取一个错误切片,并对切片中的每个错误进行递归调用`as`函数进行匹配。如果其中有任何一个错误与`target`匹配,返回`true`,否则返回`false`。
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if err == nil {
continue
}
if as(err, target, targetVal, targetType) {
return true
}
}
return false
default: // 如果`err`不属于上述两种类型,则表示错误树遍历结束,返回`false`。
return false
}
}
}
// 最后,定义了一个`errorType`变量,用于表示`error`接口的反射类型。
var errorType = reflectlite.TypeOf((*error)(nil)).Elem()