go语言中的errors

159 阅读4分钟

在 Go 语言中,error 接口是非常基础的,它定义了一个 Error() 方法,该方法返回一个描述错误的字符串。这种设计允许开发者创建自定义的错误类型,只需实现 Error() 方法即可。

type error interface {
    Error() string
}

内置包errors中实现也很简单

package errors
func New(text string) error {
    return &errorString{text}
}

type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}

调用New方法就可以生成一个error对象

e1:=errors.New("错误1")

error对象是可以比较的,但是即使New方法入参字符串相同,两个error对象也不相等,因为是两个不同的结构体对象实例

var e1 = errors.New("错误1")
var e2 = errors.New("错误2")
fmt.Println(e1 == e2)// false

自定义error

知道了一个结构体只要实现Error()string方法就是error了,接下来就可以自定义error结构体了,可以在满足接口后再自定义一些方法

type MyErr struct {
    msg string
}

func NewError(msg string) error {
    return &MyErr{msg: msg}
}

func (m MyErr) Error() string {
    return m.msg
}
func (m MyErr) Is(error) bool {
    return true
}

errors.Is

errors.Is 函数是Go 1.13版本引入的一个非常有用的错误处理工具,它用于判断一个错误值是否等于另一个错误值,或者是否由某个特定的错误值通过调用errors.Wraperrors.WithMessage(这两个函数是github.com/pkg/errors包提供的,但在Go 1.13及以后的标准库中,通过%w动词在fmt.Errorf中也支持了类似的错误包装功能)等方式包装而来。

简单来说,errors.Is用于检查一个错误链中是否包含某个特定的错误。这对于在多层调用的函数中传递和识别错误非常有用,因为它允许调用者在不修改中间层代码的情况下,对错误进行精确的判断和处理。

err1 := errors.New("original error")
err2 := fmt.Errorf("something bad happened: %w", err1)
if errors.Is(err2, err1) {
    fmt.Println("ok")
} else {
    fmt.Println("no")
}

在上述例子中,会打印ok,因为在判断过程中,如果结构体有IsUnwrap方法会被调用,fmt.Errorf是拥有Unwrap方法的。 image.png

接下来看看errors.Is的源码解析

// Is 这个方法的含义是判断入参错误是否为目标错误
func Is(err, target error) bool {
    // 如果目标错误为nil,则直接判断入参错误是否为nil
    if target == nil {
       return err == target
    }
    /*
          判断目标是否为可比较类型
          map slice chan等类型是不可比较的
          int float bool string pointer 等是可比较的
          struct 要看元素是否都是可比较的类型
    */
    isComparable := reflect.TypeOf(target).Comparable()
    return is(err, target, isComparable)
}

// is 递归判断两个错误是否相等
func is(err, target error, targetComparable bool) bool {
    for {
       // 可比较并且相等
       if targetComparable && err == target {
          return true
       }

       // err有自定义的Is方法,调用Is方法自己判断
       if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
          return true
       }

       switch x := err.(type) {

       // err有自定义的Unwrap方法,调用Unwrap方法,并将返回值作为新的err继续判断
       case interface{ Unwrap() error }:
          err = x.Unwrap()
          if err == nil {
             return false
          }
       case interface{ Unwrap() []error }:
          for _, err := range x.Unwrap() {
             if is(err, target, targetComparable) {
                return true
             }
          }
          return false
       default:
          return false
       }
    }
}

errors.As

errors.As 函数允许你检查一个错误值是否可以被视为某个特定类型的错误。如果可以将错误值断言为指定的类型,则该函数将错误值转换为该类型并存储在提供的目标变量中,同时返回true。如果断言失败,则目标变量不会被修改,函数返回false

e1 := errors.New("e1 error")
e2 := errors.New("e2 error")
fmt.Println(errors.As(e1, &e2)) // true
fmt.Println(e2.Error())         // e1 error

源码解析

func As(err error, target any) bool {
    if err == nil {
       return false
    }
    val := reflect.ValueOf(target)
    targetType := val.Type().Elem()
    return as(err, target, val, targetType)
}

// as 循环、递归判断
func as(err error, target any, targetVal reflect.Value, targetType reflect.Type) bool {
    for {
       // 判断是否可以赋值,如果可以,则将err赋值给target,并返回true
       if reflect.TypeOf(err).AssignableTo(targetType) {
          targetVal.Elem().Set(reflect.ValueOf(err))
          return true
       }
       // err有自定义的As方法,调用As方法自己判断
       if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
          return true
       }
       switch x := err.(type) {

       // err有自定义的Unwrap方法,调用Unwrap方法,并将返回值作为新的err继续判断(处理嵌套error)
       case interface{ Unwrap() error }:
          err = x.Unwrap()
          if err == nil {
             return 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:
          return false
       }
    }
}

语法解析

  • err实现了errors接口,它还有一些自己的方法,err.(interface{ As(any) bool }) 这个语法是将err类型断言为
interface{ 
   As(any) bool 
}

如果断言成功,说明err结构体上有As(any) bool这个方法