如何使用Golang errors Is判断错误类型_Golang错误比较与判断方法

errors.Is不能判断自定义错误底层类型,因其仅通过调用错误链中各错误的Is方法判断语义相等,不进行类型断言或反射;若自定义错误未实现Is方法,则errors.Is恒返回false。

errors.Is 为什么不能判断自定义错误的底层类型

errors.Is 只检查错误链中是否存在「语义相等」的错误,即调用 Is(error) 方法返回 true 的错误。它不关心底层结构体类型,也不做类型断言或反射比对。

常见误解是以为 errors.Is(err, myErr) 能识别你定义的 *MyError 类型 —— 实际上,除非你显式实现了 Is 方法并让它返回 true,否则它永远返回 false

  • 标准库中的 os.ErrNotExistio.EOF 等能被 errors.Is 识别,是因为它们本身实现了 Is 方法
  • fmt.Errorf("xxx: %w", err) 包装后的错误,仍保留原错误的 Is 行为(前提是被包装的 err 支持)
  • 直接用 errors.New("xxx")fmt.Errorf("xxx") 创建的错误,不支持 Is 比较,只能靠 errors.As 或类型断言

什么时候该用 errors.As 而不是 errors.Is

当你需要获取错误的具体值(比如访问字段、调用方法),而不是只判断「是否等于某个哨兵错误」时,errors.As 是唯一选择。

例如:你定义了带状态码和消息的错误类型 *HTTPError,想取出它的 Code 字段 —— 这必须用 errors.As 提取指针,errors.Is 完全无能为力。

  • errors.Is 返回 bool,适合 if/else 分支逻辑(如「如果文件不存在就创建」)
  • errors.As 返回 bool + 填充目标变量,适合需进一步操作错误值的场景
  • 若错误链里有多个同类型错误,errors.As 找到第一个匹配的并停止;不会继续向上遍历
var httpErr *HTTPError
if errors.As(err, &httpErr) {
    log.Printf("HTTP error code: %d", httpErr.Code)
}

自定义错误实现 Is 方法的最小必要写法

要让自己的错误能被 errors.Is 正确识别,必须在错误类型上实现 Is(target error) bool 方法,并确保逻辑可传递、无环、不依赖地址比较。

  • 不要用 == 直接比较接收者和 target(地址不同就失败)
  • 推荐方式:用 errors.As 尝试将 target 转成你的类型,再做字段级比较(如错误码、字符串消息)
  • 若你的错误只作为哨兵(如 ErrTimeout),可直接返回 target == ErrTimeout,但必须保证它是包级变量且永不重分配
type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string { return e.Msg }
func (e *MyError) Is(target error) bool {
    var t *M

yError if errors.As(target, &t) { return e.Code == t.Code } return false }

errors.Is 在多层包装下的行为边界

errors.Is 会沿着 %w 包装链逐层调用 Unwrap(),直到遇到 nil 或某层返回 true。但它不会进入非标准包装(如自定义 WrappedError 但没实现 Unwrap)。

  • 如果中间某层错误的 Unwrap() 返回 nil,链就断了,后续错误不可达
  • 如果某层 Unwrap() 返回自身(造成循环),errors.Is 会 panic(Go 1.20+ 加了检测)
  • fmt.Errorf("wrap: %w", err) 是安全的;但手动实现 Unwrap 时务必确保返回的是另一个错误,不是自己

真正容易被忽略的是:即使你用了 %w,如果被包装的错误本身不支持 Is(比如纯字符串错误),那整条链对 errors.Is 来说仍是“不可识别”的 —— 它不会自动比对错误消息字符串。