Go 1.13 的 errors.Is 和 errors.As 用于递归判断错误链中的目标错误或类型,需配合 fmt.Errorf("%w") 包装和自定义 Unwrap() 方法;直接 == 或类型断言会失败,%w 必须是 fmt.Errorf 唯一且末尾的动词。

Go 1.13 的 errors.Is 和 errors.As 怎么用才不踩坑
Go 1.13 引入错误包装(error wrapping)后,errors.Is 和 errors.As 成为判断错误类型和提取底层错误的核心工具——但它们**不是简单替代 == 或类型断言**。
常见错误是直接对包装后的错误做 err == io.EOF 或 err.(*os.PathError),结果永远失败。因为包装链中原始错误被嵌套在 fmt.Errorf("...: %w", err) 生成的结构体里。
-
errors.Is(err, io.EOF)会递归检查整个错误链,找到任意一层是io.EOF就返回true -
errors.As(err, &target)会逐层尝试类型断言,成功则把匹配到的错误赋值给target(注意传指针) - 如果错误链里有多个同类型错误,
errors.As只返回最内层(第一个匹配到的) - 不要对非包装错误(如裸
errors.New)滥用errors.As,它仍能工作,但无包装意义
%w 格式动词必须严格配对 fmt.Errorf 才生效
只有 fmt.Errorf 中显式使用 %w(且只支持一个),才会生成可被 errors.Unwrap 解包的包装错误;其他方式(比如拼接字符串、用 %s 插入错误)都会丢失包装能力。
- ✅ 正确:
fmt.Errorf("read failed: %w", io.ErrUnexpectedEOF) - ❌ 错误:
fmt.Errorf("read failed: %s", io.ErrUnexpectedEOF)→ 得到普通字符串错误,无法Is或As - ❌ 错误:
fmt.Errorf("read failed: %w, retry=%d", io.ErrUnexpectedEOF, n)→ 编译报错:格式动词%w必须是最后一个参数 - ⚠️ 注意:
%w后面不能跟其他动词,也不能出现在多参数fmt.Errorf的中间位置
自定义错误类型如何支持包装和解包
如果你写自己的错误类型并希望它能参与 errors.Is/errors.As 流程,必须实现 Unwrap() error 方法。标准库中 *os.PathError、*net.OpError 都已实现。
立即学习“go语言免费学习笔记(深入)”;
- 单层包装:返回你持有的底层错误(如字段
Err error) - 多层包装:可以返回
nil(表示无更多包装),或返回另一个实现了Unwrap()的错误 - 不要在
Unwrap()中返回自身或循环引用,否则errors.Is可能无限递归 - 示例:
type MyError struct { Msg string Err error // 底层错误,可能为 nil } func (e *MyError) Error() string { return e.Msg } func (e *MyError) Unwrap() error { return e.Err }
性能与调试时容易忽略的细节
错误包装本身开销极小,但过度包装(比如每层都加日志上下文)会让错误链变长,影响 errors.Is 查找效率,也加大了 fmt.Printf("%+v", err) 的输出体积。
- 调试时用
%+v而不是%v才能看到完整包装链(含文件行号) -
errors.Unwrap(err)只解一层;要获取最底层错误,得循环调用直到返回nil - 日志记录建议用
fmt.Sprintf("%+v", err),而不是err.Error(),否则丢失堆栈和包装信息 - HTTP handler 等入口处捕获错误后,应优先用
errors.Is做语义判断(如是否为os.ErrNotExist),再决定返回 404 还是 500,而非依赖错误字符串匹配










