Golang使用errors.Is判断错误的正确方式

11次阅读

Errors.Is常返回false,因其仅检查错误链中是否存在同一底层错误值或指针,不支持字符串匹配;须用%w包裹、预定义变量或自定义Unwrap方法才能正确识别。

Golang使用errors.Is判断错误的正确方式

errors.Is 为什么经常返回 false?

因为 errors.Is 只检查错误链中是否存在某个**底层错误值(value)或其指针**,不支持字符串匹配、模糊比较或自定义逻辑。如果你用 errors.New("timeout") 创建的错误和另一个同内容的 errors.New("timeout") 比较,errors.Is 一定返回 false —— 它们是两个不同地址的指针。

必须用 errors.New 或 fmt.Errorf 包装原始错误才能用 errors.Is

只有当错误被显式地用 fmt.Errorf("...: %w", err)errors.Join 等方式包裹(即包含 %w 动词),它才进入错误链;否则 errors.Is 查不到。

  • ✅ 正确:用 %w 包裹底层错误
  • ❌ 错误:只用 %s 或拼接字符串,如 fmt.Errorf("failed: %s", err.Error())
  • ⚠️ 注意:errors.Unwrap 只能解一层,而 errors.Is 会自动遍历整个链(包括嵌套的 %w
err := io.EOF wrapped := fmt.Errorf("read failed: %w", err) fmt.Println(errors.Is(wrapped, io.EOF)) // true

自定义错误类型要实现 Unwrap 方法

如果自己定义了错误结构体(比如 type MyError Struct{ Msg String; Code int }),默认 errors.Is 查不到它是否等于某个值,除非你显式实现 Unwrap() error 并返回底层错误。

  • 若该错误本身是终端错误(无底层),Unwrap 应返回 nil
  • 若它封装了另一个错误(如调用了 io.ReadFull),就返回那个错误
  • 不要在 Unwrap 中返回新构造的 errors.New —— 地址不同,errors.Is 仍失败
type MyError struct {     Err error }  func (e *MyError) Error() string { return "my error" } func (e *MyError) Unwrap() error { return e.Err } // ✅ 这样 errors.Is 才能穿透

判断标准错误时,优先用预定义变量而非 errors.New

go 标准库导出的错误(如 io.EOFos.ErrNotExist)是包级变量,地址唯一。直接拿它们做 errors.Is(err, io.EOF) 是安全且推荐的;但千万别写成 errors.Is(err, errors.New("EOF"))

立即学习go语言免费学习笔记(深入)”;

  • errors.Is(err, io.EOF)
  • errors.Is(err, os.ErrNotExist)
  • errors.Is(err, errors.New("file does not exist"))
  • ⚠️ 第三方库若没暴露错误变量,应查文档看它是否实现了 Unwrap 或提供了 IsXXX() 辅助函数

实际项目中最容易漏掉的是:在中间层日志或转换错误时,下意识用了字符串拼接丢掉了 %w,导致上游再也无法用 errors.Is 判断原始错误类型。这个断点一旦出现,调试时就会发现“明明报了 EOF,却进不了 EOF 分支”。

text=ZqhQzanResources