fmt.Errorf创建嵌套error

fmt.Errorf如果含%w,则会创建wrapError

// go1.18/src/fmt/errors.go
func Errorf(format string, a ...any) error {
    p := newPrinter()
    // wrapErrs is set when the format string may contain a %w verb.
    p.wrapErrs = true
    p.doPrintf(format, a)
    s := string(p.buf)
    var err error
    // 如果格式中没有%w,则创建一个新的err,否则创建wrapError
    if p.wrappedErr == nil {
        err = errors.New(s)
    } else {
        err = &wrapError{s, p.wrappedErr}
    }
    p.free()
    return err
}

wrapErrorError() string方法,符合error接口;另外还有Unwrap() error方法

type wrapError struct {
    msg string
    err error
}

func (e *wrapError) Error() string {
    return e.msg
}

func (e *wrapError) Unwrap() error {
    return e.err
}

errors.Is递归检查嵌套error是否某个错误

循环errors.Unwrap解除嵌套,如果解出的值== target或者Is(target),就返回true

直至解出的是nil,就返回false

// go1.18/src/errors/wrap.go
func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()
    for {
        if isComparable && err == target {
            return true
        }
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

errors.Unwrap就是检查有没Unwrap接口,有就Unwrap,否则返回nil

// go1.18/src/errors/wrap.go
func Unwrap(err error) error {
    u, ok := err.(interface {
        Unwrap() error
    })
    if !ok {
        return nil
    }
    return u.Unwrap()
}

errors.As抽取链中某类型的error,并赋值到另一个变量中

一般用法:创建一个某类型的指针的空变量,将该指针传递给errors.As,则该指针能赋值为同类型error的报错信息

var badInputErr *BadInputError
if errors.As(err, &badInputErr) {
    fmt.Printf("bad input error occured: %s\n", badInputErr)
}

也是循环Unwrap,然后检查能否赋值

func As(err error, target any) bool {
    if target == nil {
        panic("errors: target cannot be nil")
    }
    val := reflectlite.ValueOf(target)
    typ := val.Type()
    if typ.Kind() != reflectlite.Ptr || val.IsNil() {
        panic("errors: target must be a non-nil pointer")
    }
    targetType := typ.Elem()
    if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
        panic("errors: *target must be interface or implement error")
    }
    for err != nil {
        if reflectlite.TypeOf(err).AssignableTo(targetType) {
            val.Elem().Set(reflectlite.ValueOf(err))
            return true
        }
        if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
            return true
        }
        err = Unwrap(err)
    }
    return false
}

参考

- Go 源码解读|如何用好 errors 库的 errors.Is() 与 errors.As() 方法 - 白泽来了 - 博客园