使用%w而非%v包装错误可保留原始错误类型和堆栈,使errors.Is()和errors.As()有效;%w要求参数为error类型,空值需提前判空,否则导致nil链或潜在panic。

fmt.Errorf 包装错误时为什么原始错误丢失了
直接用 fmt.Errorf("something went wrong: %v", err) 会丢掉原始错误的类型和堆栈,因为 %v 只调用 err.Error(),返回纯字符串。下游无法用 errors.Is() 或 errors.As() 判断或提取原始错误。
必须用 %w 才能保留错误链
%w 是 Go 1.13 引入的动词,专用于包装错误并保留底层错误的可检查性。它要求参数是 error 类型,否则编译报错:cannot use ... as error value in argument to fmt.Errorf: missing method Error。
- 正确写法:
err := fmt.Errorf("failed to open config: %w", os.Open("config.json")) - 错误写法(丢失链):
err := fmt.Errorf("failed to open config: %v", os.Open("config.json")) - 嵌套多层也有效:
err := fmt.Errorf("processing failed: %w", fmt.Errorf("validation error: %w", validationErr))
链式错误的实际判断与提取场景
用 %w 包装后,才能在上层做语义化错误处理。比如重试逻辑只对网络超时重试,忽略权限错误;或日志中只打印最外层消息,但调试时展开全部原因。
- 判断是否为某类错误:
if errors.Is(err, os.ErrNotExist) { /* 处理文件不存在 */ } - 提取具体错误类型:
var pathErr *os.PathError if errors.As(err, &pathErr) { /* 获取路径、操作等细节 */ } - 打印完整错误链(Go 1.20+):
fmt.Printf("%+v\n", err) // 显示每一层 + 堆栈
注意 wrap 后的错误类型和 nil 行为
fmt.Errorf(... %w) 返回的仍是 *fmt.wrapError 类型,不是原错误类型;且只要任一环节返回 nil,整个链就变成 nil —— 这点容易被忽略。
立即学习“go语言免费学习笔记(深入)”;
- 避免空包装:
if err != nil { return fmt.Errorf("read header: %w", err) } return nil // 不要写成 fmt.Errorf("read header: %w", nil) - 包装前务必判空,否则
%w遇到nil会 panic(实际不会 panic,但结果是fmt.Errorf("msg: %w", nil)返回nil,可能引发空指针后续问题) - 自定义错误类型实现
Unwrap() error也能参与链式判断,但多数情况直接用%w更轻量
链式错误不是加几层 %w 就完事,关键在每层包装都得有明确语义,且下游真会用 Is/As 做分支处理;否则只是徒增堆栈深度。










