必须用%w包装错误,否则errors.is和errors.as无法识别原始错误;核心判断标准是上层是否需判断、提取或响应底层错误类型或值。

必须用 %w 包装,否则上层无法用 errors.Is 或 errors.As 识别原始错误——这不是可选项,是链式追踪生效的前提。
什么时候必须用 %w 而不是 %v 或字符串拼接
核心只看一点:上层是否需要判断、提取或响应底层错误的类型或值。比如检查是不是 os.ErrNotExist、断言是不是 *os.PathError、或者调用自定义错误的 Code() 方法。
-
fmt.Errorf("read %s: %v", path, err)→ 类型丢失,errors.Is(err, os.ErrNotExist)永远返回false -
"failed: " + err.Error()→ 不仅丢类型,还可能因err是nil导致 panic -
fmt.Errorf("read %s: %w", path, err)→ 正确:保留引用,自动实现Unwrap(),链可穿透
errors.Is 和 errors.As 为什么不能用 == 替代
== 只比较最外层错误值,而真实错误常被多层包装。例如:
err := fmt.Errorf("handler: %w", fmt.Errorf("db: %w", os.ErrNotExist))
此时 err == os.ErrNotExist 是 false,但 errors.Is(err, os.ErrNotExist) 是 true。
-
errors.Is先做==,再逐层errors.Unwrap()直到匹配或链结束 -
errors.As尝试将链中**任意一层**的错误赋值给目标指针(如&pathErr),不保证是最内层 - 单元测试里必须用
errors.Is(err, wantErr),否则加一层包装就失败
自定义错误类型如何真正参与错误链
默认结构体错误(哪怕实现了 Error())在链中是“死路”——errors.Is 和 errors.As 完全跳过它。
- 必须显式实现
Unwrap() error方法,返回被包装的底层error字段 - 如果该错误不包装其他错误(如纯业务码错误),
Unwrap()应返回nil,不能返回自身或未初始化字段 -
Unwrap()必须无副作用:不能打日志、不能修改状态、不能 panic - 示例:
func (e *MyError) Unwrap() error { return e.Cause },其中Cause是error类型字段
errors.Unwrap 的真实用途和常见误用
errors.Unwrap 是单层解包函数,不是“展开全链工具”。它只返回下一层错误,返回 nil 表示链终止。
- 不要写无限循环:
for err != nil { err = errors.Unwrap(err) }—— 某些错误(如日志包装器)可能返回自身,导致死循环 - 若需遍历全链,应加深度限制(如最多 10 层)并检查
unwrapped == err - 日常开发中优先用
errors.Is/errors.As,而不是手动Unwrap;后者主要用于调试、日志或特殊诊断逻辑 -
errors.Join返回的错误,Unwrap()返回nil,但它支持errors.Is对每个子错误分别匹配
最容易被忽略的是:错误链的价值不在“怎么包”,而在“怎么读”——日志系统是否能解析 %+v、监控告警是否基于 errors.Is 判断、中间件是否统一用 %w 注入上下文。链本身很轻,但上下游配合松动一环,整条链就失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











