使用%w而非%v包装错误可保留原始错误类型和堆栈,使errors.Is()和errors.As()有效;%w要求参数为error类型,空值需提前判空,否则导致nil链或潜在panic。
直接用 fmt.Errorf("something went wrong: %v", err) 会丢掉原始错误的类型和堆栈,因为 %v 只调用 err.Error(),返回纯字符串。下游无法用 errors.Is() 或 errors.As() 判断或提取原始错误。
%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) { /* 获取路径、操作等细节 */ }fmt.Printf("%+v\n", err) // 显示每一层 + 堆栈fmt.Errorf(... %w) 返回的仍是 *fmt.wrapError 类型,不是原错误类型;且只要任一环节返回 n,整个链就变成 
nil —— 这点容易被忽略。
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 做分支处理;否则只是徒增堆栈深度。