Go errors.Is()和errors.As()的区别

9,424 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22天,点击查看活动详情

使用fmt.Errorf()包装error后,会得到一个wrapError类型的错误。针对包装过的error,erros包还提供了下面3个方法。本文着重来讲下errors.Is()和errors.As的区别

func Unwrap(err error) error                 // 获得err包含下一层错误
func Is(err, target error) bool              // 判断err是否包含target
func As(err error, target interface{}) bool  // 判断err是否为target类型

errors.Is()

作用:判断被包装过的error是否包含指定错误

示例代码:

var BaseErr = errors.New("base error")
​
func main() {
   err1 := fmt.Errorf("wrap base: %w", BaseErr)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   println(err2 == BaseErr)
   
   if !errors.Is(err2, BaseErr) {
      panic("err2 is not BaseErr")
   }
   println("err2 is BaseErr")
}

//输出:
//false
//err2 is BaseErr

来看一下 errors.Is 方法的源码:

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
      }
   }
}

func Unwrap(err error) error {
  u, ok := err.(interface {
    Unwrap() error
  })
  if !ok {
    return nil
  }
  return u.Unwrap()
}

源码分析:如果这个 err 实现了 interface{ Is(error) bool } 接口,通过接口断言,可以调用 Is 方法判断 err 是否与 target 相等。否则递归调用 Unwrap 方法拆包装,返回下一层的 error 去判断是否与 target 相等。

errors.As()

作用:判断被包装过的error是否为指定类型

具体说明:提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target

示例代码:

type TypicalErr struct {
   e string
}

func (t TypicalErr) Error() string {
   return t.e
}

func main() {
   err := TypicalErr{"typical error"}
   err1 := fmt.Errorf("wrap err: %w", err)
   err2 := fmt.Errorf("wrap err1: %w", err1)
   var e TypicalErr
   if !errors.As(err2, &e) {
      panic("TypicalErr is not on the chain of err2")
   }
   println("TypicalErr is on the chain of err2")
   println(err == e)
}
//输出:
//TypicalErr is on the chain of err2
//true

来看一下 error.As 方法的源码:

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
}

源码分析

  • for 循环前的部分是用来约束 target 参数的类型,要求其是一个非空的指针类型
  • 此外要求 *target 是一个接口或者实现了 error 接口
  • for 循环判断 err 是否可以赋值给 target 所属类型,如果可以则赋值返回 true
  • 如果 err 实现了自己的 As 方法,则调用其逻辑,否则也是走递归拆包的逻辑

如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!