%w 包装错误时原错误丢失是因为它只接受非 nil 的 error 类型参数,传入 string、nil 或格式干扰(如紧邻字符)会导致忽略或 panic;必须用 %w 独立占位并逐层包装才能构建有效错误链。

fmt.Errorf("%w") 包装错误时为什么原错误丢失了
因为 %w 只接受实现了 error 接口的单个值,传入 nil、非 error 类型或多个参数都会导致 panic 或静默失败。常见错误是误把字符串拼接逻辑混进来,比如 fmt.Errorf("failed: %w", err.Error()) —— 这里 err.Error() 返回的是 string,不是 error,%w 直接忽略它,最终返回一个不带包装的普通错误。
- 必须确保
%w对应的参数是error类型,且非nil(nil 会触发 runtime error) - 不能和
%s混用在同一占位位置,例如fmt.Errorf("wrap %w: %s", err, msg)是合法的,但fmt.Errorf("wrap %w%s", err, msg)会让%w后面紧挨着字符,Go 解析器会报错 - 如果要组合多个错误,只能逐层包装:
fmt.Errorf("level2: %w", fmt.Errorf("level1: %w", origErr))
如何正确用 errors.Is / errors.As 判断被 %w 包装的错误
errors.Is 和 errors.As 依赖错误链(error chain)向后遍历,只有用 %w(或 fmt.Errorf("…%w", err))包装的错误才构成标准链;用 + 拼接、sprintf、fmt.Errorf("…%v", err) 都不会建立链路,判断必然失败。
-
errors.Is(err, io.EOF)成功的前提是:err 是由%w一路包装过来的,且最内层是io.EOF或其等价 error -
errors.As(err, &target)要求 target 是指针,且 err 链中某一级的动态类型能赋值给 target 所指类型(比如自定义 error 类型) - 注意:如果中间某层用了
%v或%s输出错误文本,那之后的链就断了——%w必须是独立占位符,不可被格式干扰
什么时候不该用 %w:性能敏感路径和日志透出场景
每次用 %w 包装都会新建一个运行时错误对象,并在内部维护一个指针链;对高频调用路径(如网络请求每秒万级)或只用于打日志、不需下游判断的错误,过度包装反而增加 GC 压力和堆分配开销。
- 日志场景优先用
fmt.Sprintf("req failed: %v", err),避免无意义的链式结构 - HTTP handler 中返回给客户端的错误,一般不应暴露底层
%w链(容易泄露内部细节),而应统一转成fmt.Errorf("internal error")或 HTTP 状态码响应 - 如果只是加一句上下文说明,且不打算用
errors.Is判断,用fmt.Errorf("context: %v", err)更轻量
自定义 error 类型与 %w 共存时的典型陷阱
自定义 error 如果自己实现了 Error() 方法但没实现 Unwrap(),那么用 %w 包装它后,errors.Is 就无法穿透到内层——Go 的错误链机制依赖显式 Unwrap() 方法返回下一个 error。
立即学习“go语言免费学习笔记(深入)”;
- 要支持标准链,必须让自定义类型实现
Unwrap() error,返回被包装的 err 字段 - 如果同时想保留原始 error 的 stack trace(比如用
github.com/pkg/errors),注意不要和 Go 1.13+ 原生%w混用,否则Unwrap()可能返回非 error 类型或引发循环 - 测试时可用
errors.Unwrap(err)手动展开一层,确认是否返回预期 error,比盲猜更可靠










