%w仅接受error接口值,包装后支持errors.Is/As和%+v展开;多错误需自定义类型或分层包装,非error值会导致编译失败。

fmt.Errorf 用 %w 包装错误时,底层必须是 error 类型
不是所有值都能被 %w 包装——它只接受实现了 error 接口的值。传入字符串、nil、结构体实例(没实现 Error() 方法)都会导致编译失败或运行时 panic。
- ✅ 正确:用
fmt.Errorf("failed to open %s: %w", path, os.Open(path)) - ❌ 错误:用
fmt.Errorf("failed: %w", "plain string")—— 编译报错:cannot use "plain string" (type string) as type error - ⚠️ 注意:
nil是合法的error值,但%w包装nil后,errors.Is(err, target)仍可能返回 false,因为包装后形成新 error,需用errors.Unwrap或检查是否为nil底层
想用 errors.Is 或 errors.As 判断原始错误?必须用 %w,不能用 %v 或字符串拼接
%w 的唯一作用,就是让被包装的 error 可被标准库错误检查函数识别。一旦换成 %v、+ "" 或 sprintf 拼接,原始 error 就彻底丢失,变成纯文本。
- ✅ 可检测:
err := fmt.Errorf("db query failed: %w", sql.ErrNoRows)→errors.Is(err, sql.ErrNoRows)返回 true - ❌ 不可检测:
err := fmt.Errorf("db query failed: %v", sql.ErrNoRows)→errors.Is(err, sql.ErrNoRows)返回 false - ? 性能提示:
%w包装不拷贝底层 error 数据,只是加一层 wrapper,开销极小;但过度嵌套(如 10 层以上)可能影响errors.Unwrap遍历效率
多个错误要一起包装?%w 只支持一个,别硬塞
fmt.Errorf 的 %w 动词只允许出现一次,且只能对应一个 error 参数。试图写成 "%w and %w" 会编译失败;想保留多个独立错误线索,得换思路。
- ❌ 错误写法:
fmt.Errorf("read+write failed: %w, %w", readErr, writeErr)→ 编译错误:multiple %w verbs - ✅ 替代方案 1:用
fmt.Errorf("read failed: %w; write failed: %w", readErr, writeErr)—— 但第二个%w无效,实际只包装了readErr - ✅ 替代方案 2:自定义 error 类型,内嵌多个 error 字段,并实现
Unwrap()返回第一个或切片(需自行控制逻辑) - ✅ 替代方案 3:用第三方包如
github.com/pkg/errors(已归档)或更现代的golang.org/x/xerrors(已合并进标准库),但当前 Go 1.20+ 推荐直接用标准库 + 自定义
日志里打印带 %w 的 error,看不到原始错误堆栈?那是没用 %+v
默认 fmt.Printf("%v", err) 只显示最外层错误文本,不展开包装链;调试时容易误判“错误没传下去”。真正要看全链,得用 %+v。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 查看完整链:
log.Printf("%+v", err)→ 输出含每一层 error 和文件/行号(如果底层 error 支持,比如fmt.Errorf自带) - ⚠️ 兼容性注意:Go 1.13+ 才支持
%+v对包装 error 的展开;旧版本只会输出%v效果 - ? 实操建议:在 dev 环境日志统一用
%+v,prod 可考虑%v避免敏感路径泄露,但务必确保关键错误至少有一次%+v记录
%w 就完事——它把 error 变成有向链表,而 errors.Is / errors.As / %+v 这些能力,都依赖这条链没被意外截断。最容易被忽略的是:上游函数返回 nil error 时,你还用 %w 包装,结果得到一个非 nil 的 wrapper,但 Unwrap() 出来是 nil,这时候 Is 和 As 行为就和直觉不一样了。










