go 1.20+ 中错误链式处理核心是用 fmt.errorf("%w", err) 包装以支持 errors.unwrap/is/as 追溯;避免 %v 丢失类型,慎用 pkg/errors;errors.join 用于聚合多错误;%+v 可打印完整调用链。

Go 1.20+ 中用 fmt.Errorf 的 %w 动词包装错误
链式返回错误的核心是让下游能通过 errors.Unwrap 或 errors.Is/errors.As 向上追溯原始错误。从 Go 1.20 开始,fmt.Errorf 支持 %w(wrap)动词,这是最轻量、最标准的包装方式。
常见错误是写成 fmt.Errorf("failed to open file: %v", err) —— 这会丢失原始错误类型和堆栈,变成纯字符串。
- 正确写法:
return fmt.Errorf("failed to open file: %w", err) -
%w只接受一个实现了error接口的值,且必须是最后一个参数 - 被包装的错误可通过
errors.Unwrap(err)获取(只解一层),或用errors.Is(err, targetErr)判断是否包含某类错误 - 如果 err 是 nil,
%w会静默忽略,结果为非 error 值(即包装后仍是 nil),这点和%v不同
用 errors.Join 合并多个错误并保持可遍历性
当一个操作可能触发多个独立失败(如并发调用多个服务),需要聚合错误又不想丢弃任一原因时,errors.Join 是 Go 1.20 引入的官方方案。
它返回的 error 实现了 Unwrap() []error,支持递归展开,比手拼字符串或自定义 struct 更可靠。
立即学习“go语言免费学习笔记(深入)”;
- 示例:
err := errors.Join(errA, errB, errC),后续可用errors.Unwrap(err)得到切片 -
errors.Is(err, someErr)会对所有子错误逐个调用Is,任一匹配即返回 true - 注意:若传入 nil,
Join会跳过;但若全为 nil,则返回 nil - 不适用于需保留调用上下文的场景(它不自动加堆栈),此时应配合
fmt.Errorf("%w", ...)分层包装
避免在中间层用 errors.Wrap(来自 github.com/pkg/errors)
很多老项目还在用 github.com/pkg/errors 的 Wrap,但它与标准库不兼容:其 Wrap 返回的 error 不满足 Go 标准库的 Unwrap() 签名(返回单个 error 而非可迭代结构),导致 errors.Is/errors.As 在跨包调用时失效。
- Go 1.13 引入的
%w和 Go 1.20 的Join已覆盖全部需求,无需第三方包装器 - 如果依赖中仍含
pkg/errors,升级时要检查是否直接用了它的Wrap/WithStack;建议逐步替换为fmt.Errorf("%w", ...) - 特别注意:
pkg/errors.Cause(err)在标准库 error 链上行为不可靠,不要混用
调试时用 fmt.Printf("%+v", err) 查看完整错误链
标准库的 %+v 动词会打印错误链中每一层的文本 + 调用位置(前提是使用 fmt.Errorf("%w", ...) 包装,且运行时启用了 GODEBUG=gocacheverify=1 或 Go 1.21+ 默认开启的 stack trace 捕获)。
- 对比:
fmt.Println(err)只输出最外层错误文本;fmt.Printf("%+v", err)展开整个链并显示每层的文件/行号 - 该行为依赖
runtime/debug.Stack(),在 CGO 环境或某些嵌入式目标下可能被裁剪,需确认构建环境 - 生产环境慎用
%+v打印敏感路径或参数,建议日志中仅用%v或提取关键字段
os.IsNotExist),直接处理而非层层包装,否则会让调用方难以区分语义。










