应改用 fmt.errorf 的 %w 动词而非 xerrors.errorf,因 xerrors 已弃用;%w 仅支持单个 error 参数且须位于格式串末尾,配合 errors.is/as 才能正确遍历错误链。

什么时候该用 xerrors.Errorf 而不是 fmt.Errorf
Go 1.13 引入了标准库的错误链支持(errors.Unwrap、errors.Is、errors.As),而 xerrors 是它在正式落地前的实验性实现。现在 xerrors 已被弃用,官方明确建议迁移到 fmt.Errorf 的 %w 动词——它和 xerrors.Errorf 行为一致,且无需额外依赖。
如果你还在用 xerrors.Errorf("failed: %w", err),直接换成 fmt.Errorf("failed: %w", err) 即可,语义、展开行为、性能都完全等价。
-
%w只接受一个error类型参数,多于一个会 panic - 未使用
%w的fmt.Errorf(比如只用%s)不会形成错误链,只是普通字符串包装 - Go 1.20+ 中
xerrors已无法通过go get安装,模块校验会失败
%w 的正确写法和常见翻车点
错误链不是“加了 %w 就自动可查”,关键在于:只有最内层错误实现了 Unwrap() error 方法,外层才能逐级展开。而 fmt.Errorf 带 %w 时,会自动实现该方法;不带则不会。
下面这些写法看似相似,但错误链能力完全不同:
err := fmt.Errorf("read config: %w", io.EOF) // ✅ 可展开,io.EOF 是底层错误
err := fmt.Errorf("read config: %s", io.EOF) // ❌ 不可展开,io.EOF 被转成字符串
err := fmt.Errorf("read config: %v", io.EOF) // ❌ 同上,%v 不触发 wrap
err := fmt.Errorf("read config: %w, retry=%d", err, 3) // ❌ 编译失败:多个参数不能混用 %w
- 必须把
%w放在格式字符串的最后,且只能出现一次 - 如果要拼接非错误信息,先用字符串处理好,再整体传给
%w,例如:fmt.Errorf("read %s: %w", filename, err) -
%w会保留原始错误的类型和值,但不会保留其自定义的Error()描述——那是上层包装者的责任
如何检查和提取错误链中的特定错误
有了 %w 包装后,就不能再靠 == 或 strings.Contains(err.Error(), "...") 来判断错误类型了。必须用标准库的 errors.Is 和 errors.As。
比如你想确认是不是因为文件不存在导致的失败:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
if errors.Is(err, os.ErrNotExist) {
log.Println("file missing")
}
或者想拿到底层的 *os.PathError 并访问其字段:
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("failed on path: %s", pathErr.Path)
}
-
errors.Is会沿着Unwrap()链一直往下比对,直到 nil 或匹配成功 -
errors.As同样遍历链,但要求目标指针类型能匹配某一层的具体错误类型 - 如果中间某层错误自己实现了
Unwrap()但返回了 nil(比如某些自定义错误的早期写法),链就断了——这是容易被忽略的兼容性陷阱
自定义错误类型要不要实现 Unwrap
绝大多数情况下不用。除非你封装的是一个“代理型”错误(比如重试逻辑中聚合多次失败),需要把多个底层错误同时暴露出去,才考虑手动实现 Unwrap() error 或 Unwrap() []error(后者需配合 errors.Join)。
普通业务错误类型,比如:
type ConfigParseError struct {
File string
Err error
}
func (e *ConfigParseError) Error() string {
return fmt.Sprintf("parse %s: %v", e.File, e.Err)
}
这种写法没有实现 Unwrap,所以它会切断错误链——即使你把 e.Err 存下来了,调用方也拿不到。
- 如果想保持链式访问,必须显式实现:
func (e *ConfigParseError) Unwrap() error { return e.Err } - 注意:不要在
Unwrap()里做任何可能 panic 或阻塞的操作,它是被频繁调用的基础方法 - 也不建议在
Unwrap()里返回新构造的错误(如fmt.Errorf(...)),这会造成链污染和内存泄漏风险
真正需要手动干预错误链的地方比想象中少得多;多数时候,老老实实用 fmt.Errorf("xxx: %w", err) 就够了,别过早抽象。










