%w 是 Go 错误链唯一可靠包装方式,因其强制 error 类型校验、生成 Unwrap() 方法且仅允一个;errors.Is 和 errors.As 通过逐层 Unwrap() 穿透判断;自定义错误需实现 Unwrap();errors.Join 用于并发多错聚合,非因果链。

fmt.Errorf 用 %w 包装、errors.Is 判断、errors.As 提取——这三者是 Go 错误链唯一可靠组合。不用 %w,就不是错误链;只靠 == 或 %v 拼接,链必然断裂。
为什么 fmt.Errorf("xxx: %w", err) 是唯一标准包装方式
Go 错误链不是自动形成的,而是靠 %w 显式注入引用关系。它要求参数必须是 error 类型,编译器会强制校验,避免传入字符串或 nil 导致静默失败。
-
%w不是格式化占位符,是“包装指令”:生成的新错误内部持有对原错误的引用,并实现Unwrap() error方法 - 一个
fmt.Errorf调用中只能有一个%w,多用会编译报错:multiple %w verbs - 用
%v、%s或+拼接会把原始错误转成字符串,errors.Unwrap返回nil,下游无法追溯 - 包装层应添加**语义变化**,比如从 “打开文件” 到 “加载配置”,而不是重复写 “处理失败: %w”
errors.Is 和 errors.As 怎么穿透多层判断
它们不是魔法,而是逐层调用 Unwrap() 直到匹配目标错误或返回 nil。直接用 == 只比最外层,99% 场景都会失效。
-
errors.Is(err, os.ErrNotExist)→ 正确,无论包装几层都返回true -
err == os.ErrNotExist→ 错误,仅当err就是os.ErrNotExist本身才成立 -
errors.As(err, &pathErr)→ 必须传指针(&pathErr),且pathErr类型要具体(如*os.PathError),不能是error - 若链中有多个同类型错误,
errors.As只返回第一个匹配项,不会跳过中间层
自定义错误类型怎么接入标准错误链
只要实现了 Unwrap() error 方法,就能被 errors.Is 和 errors.As 正常穿透。漏掉这个方法,你的错误就是链的终点,再往下就断了。
- 字段命名不重要,关键是
Unwrap()返回一个error(可以是nil,表示无下层) - 推荐用指针接收者实现,避免值拷贝丢失内部状态:
func (e *MyError) Unwrap() error - 不要在
Unwrap()里做逻辑判断或重试,它只负责“暴露下一层”,保持纯函数语义 - 如果错误结构体含多个可展开字段(如
Cause和Prev),选一个主因返回即可;复杂场景建议用errors.Join替代嵌套
errors.Join 什么时候该用,什么时候不该用
errors.Join 是 Go 1.20+ 引入的并发错误聚合工具,专为“多个独立失败”设计,和 %w 的单层因果包装有本质区别。
立即学习“go语言免费学习笔记(深入)”;
- 适用场景:并发调用三个 API,其中两个失败 →
errors.Join(err1, err2) - 不适用场景:A 调 B,B 调 C,C 失败 → 这是因果链,该用
%w逐层包装,不是Join -
errors.Join返回的错误Unwrap()返回nil,但支持errors.Is遍历所有子错误 - 禁止用
fmt.Errorf("%w %w", e1, e2)模拟Join:这只是字符串拼接,不构成可解包结构
%w 包装后,fmt.Printf("%+v", err) 在 Go 1.20+ 默认也不会输出帧信息,除非你手动用 runtime.Caller 补充,或依赖 slog 的 WithGroup + 自定义 handler。链本身只管“谁导致了谁”,不管“在哪发生的”。


















