应使用 errors.Is 和 errors.As,而非手动循环调用 errors.Unwrap;二者自动处理多层嵌套、语义清晰安全,且能穿透包装器重写的 Error() 方法。

Go 1.20+ 中 errors.Unwrap 已被弃用,该用 errors.Unwrap 还是 errors.Is/errors.As?
直接说结论:errors.Unwrap 没被删除,但不再推荐手动循环调用它来“解包”错误链;真正该用的是 errors.Is 和 errors.As —— 它们内部已自动处理多层嵌套,且语义清晰、安全可靠。
手动写 for err != nil { err = errors.Unwrap(err) } 不仅冗余,还容易漏掉中间某层的包装逻辑(比如某些库用 %w 包装但没实现 Unwrap() 方法),更关键的是:它无法区分同类型错误在不同层级的语义差异。
-
errors.Is(err, target)判断错误链中是否存在某个**值相等**的错误(如io.EOF) -
errors.As(err, &target)尝试将错误链中**第一个匹配的类型**赋值给目标变量(支持自定义错误结构体) - 两者都跳过非标准包装器(即不满足
Unwrap() error签名或返回 nil 的错误)
如何正确包装错误以支持链式追踪?
必须用 %w 动词,且只在 fmt.Errorf 中使用。其他方式(如拼接字符串、用 %s 插入原错误)都会切断错误链。
err := os.Open("missing.txt")
if err != nil {
// ✅ 正确:保留原始错误,可被 Is/As 追踪
return fmt.Errorf("failed to load config: %w", err)
// ❌ 错误:丢失原始错误引用,变成纯字符串
// return fmt.Errorf("failed to load config: %s", err)
// ❌ 错误:虽然保留了 err,但没用 %w,不会被 Unwrap() 识别
// return fmt.Errorf("failed to load config: %+v", err)
}
注意:%w 要求右侧表达式类型为 error,且该值必须实现了 Unwrap() error 方法(标准库错误和大多数现代库都满足)。
立即学习“go语言免费学习笔记(深入)”;
- 若包装多个错误,
fmt.Errorf只接受一个%w,其余需用%v或转为字符串 - 自定义错误类型若想参与链式追踪,必须显式实现
Unwrap() error方法
为什么 errors.Is 有时返回 false,明明错误里包含目标?
常见于两种情况:一是错误未用 %w 包装,二是目标错误本身不是“可比较”的值(比如临时构造的 errors.New("xxx"))。
// ❌ 错误示例:每次 new 都是新地址,Is 判断失败
if errors.Is(err, errors.New("not found")) { ... } // 总是 false
// ✅ 正确:定义全局错误变量
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) { ... } // 可靠
// ✅ 或用 errors.Is + 自定义类型判断(推荐)
type NotFoundError struct{ Msg string }
func (e *NotFoundError) Error() string { return e.Msg }
func (e *NotFoundError) Is(target error) bool {
_, ok := target.(*NotFoundError)
return ok
}
if errors.Is(err, &NotFoundError{}) { ... }
-
errors.Is底层用==比较指针或值,不比较字符串内容 - 动态生成的错误(如
fmt.Errorf("xxx: %w", err)中的err是动态的)不影响判断,只要它本身是可比较的 - 若需内容匹配,应单独提取错误信息(如用
errors.Unwrap后调err.Error()),但这已脱离“错误链语义”,属于兜底策略
调试时如何快速打印完整错误链?
标准库不提供原生“展开全部”的函数,但可用 fmt.Printf("%+v", err) 查看堆栈(需错误实现 fmt.Formatter,如 github.com/pkg/errors 或 Go 1.17+ 的 fmt.Errorf 默认支持)。
更稳妥的方式是手动遍历并打印:
func PrintErrorChain(err error) {
for i := 0; err != nil; i++ {
fmt.Printf("%d. %v\n", i, err)
err = errors.Unwrap(err)
}
}
注意:这个循环只适用于你**信任所有中间错误都正确实现了 Unwrap()**。生产环境不建议依赖此逻辑做业务判断,仅用于日志或调试。
最容易被忽略的一点是:错误链的“根因”不一定在最底层。有些中间包装器会重写 Error() 方法,掩盖原始信息;而 Is/As 却能穿透这种掩盖——所以业务逻辑中永远优先用 Is/As,而不是自己解析 Error() 字符串。










