用fmt.Errorf包装错误时必须用%w动词显式传入原始错误,否则错误链断裂导致errors.Is和errors.As失效;%w保留错误类型与值以支持判断和提取,%s仅转字符串而丢失原始错误。

直接说结论:用 fmt.Errorf 包装错误时,必须显式传入原始错误(通常用 %w 动词),否则错误链断裂,errors.Is 和 errors.As 会失效。
什么时候该用 %w 而不是 %s
当你想保留底层错误的类型和值信息(比如做错误判断或提取上下文),就必须用 %w。它专为错误包装设计,会把传入的 error 嵌入到新错误中;而 %s 只是调用 .Error() 转成字符串,原始 error 对象彻底丢失。
-
fmt.Errorf("failed to open file: %w", err)→ 可用errors.Is(err, fs.ErrNotExist)判断 -
fmt.Errorf("failed to open file: %s", err)→errors.Is永远返回false - 多个包装层也支持:比如
fmt.Errorf("handler failed: %w", fmt.Errorf("db timeout: %w", dbErr)),链式调用仍可追溯到底层
fmt.Errorf 包装后如何检查原始错误
包装后的错误必须用 errors.Is 或 errors.As 向下查找,不能用 == 或 strings.Contains 判断——因为包装后已不是原 error 实例,而是新结构体。
-
errors.Is(wrappedErr, fs.ErrNotExist)→ 正确,自动遍历整个错误链 -
errors.As(wrappedErr, &pathErr)→ 正确,可提取*fs.PathError类型 -
wrappedErr == fs.ErrNotExist→ 错误,恒为false -
strings.Contains(wrappedErr.Error(), "no such file")→ 脆弱,依赖字符串内容,不推荐用于逻辑分支
常见踩坑:忘记 %w 导致错误处理失效
最典型场景是日志增强或加前缀时顺手用了 %v 或 %s,结果调试时发现 errors.Is 突然不工作了。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
return fmt.Errorf("config load error: %v", err)—— 断链 - 正确写法:
return fmt.Errorf("config load error: %w", err)—— 保链 - 如果只是想记录日志但不传播错误,就别用
fmt.Errorf,直接log.Printf或fmt.Printf - 静态检查可用
go vet(Go 1.22+ 默认启用),它会警告未使用%w却传入 error 的情况
真正容易被忽略的是:哪怕只加一层无关紧要的日志前缀,只要用了 fmt.Errorf 就必须决定是否保留错误链——这不是“要不要更详细”的风格问题,而是“能不能正常判断错误类型”的功能分水岭。










