该用 errors.New 时是需固定无变量错误描述且要轻量安全比较的场景;它不支持格式化和错误链,而 fmt.Errorf 通过 %w 动词实现标准错误包装与上下文嵌入,语义更清晰。
errors.New?当你只需要一个固定、无变量的错误描述时,errors.New 是最轻量的选择。它内部只是构造一个 *errors.errorString 结构体,开销极小,且返回的错误值可安全比较(相同字符串多次调用会得到相等的指针)。
var ErrInvalidInput = errors.New("invalid input")
"file " + name + " not found")会让代码难读、易出错%w)fmt.Errorf 而不是拼接字符串再传给 
errors.New?
手动拼接字符串再喂给 errors.New 看似可行,但实际绕开了标准错误处理的关键能力:上下文嵌入与错误包装。
fmt.Errorf("failed to parse %s: %w", filename, err) 中的 %w 可以保留原始错误,供 errors.Is 或 errors.Unwrap 追踪fmt.Errorf 底层其实也是调用 errors.New,但它先完成格式化,再构造错误——你省去的是手动 fmt.Sprintf + errors.New 两步,还获得标准语义fmt.Errorf 的 %w 动词到底怎么用?%w 是 Go 1.13 引入的专用动词,专用于包装(wrap)另一个 error 值。它不是字符串插值,而是建立错误链关系。
func readFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open %s: %w", filename, err)
}
defer f.Close()
return nil
}
// 调用方可以这样检查原始错误:
if errors.Is(err, os.ErrNotExist) {
// 处理文件不存在的情况
}
%w 参数必须是 error 类型,其他占位符(如 %s、%d)照常用fmt.Errorf 中最多只能有一个 %w;多个包装请用 fmt.Errorf("...: %w", errors.Join(err1, err2))(Go 1.20+)%w 而写成 %s,会导致原始错误被转成字符串丢弃,再也无法用 errors.Is 判断fmt.Errorf?在绝大多数业务场景下,差异可忽略。但要注意:这不是“性能问题”,而是“语义清晰度问题”。
errors.New 零分配(仅一次结构体指针),fmt.Errorf 至少有一次字符串格式化分配 —— 但在 HTTP handler 或数据库操作里,这点开销远小于 I/O 本身fmt.Errorf 包装非错误值,比如 fmt.Errorf("status code: %d", code)(code 不是 error)—— 这破坏了错误链,也误导调用方errors.New(...) 的结果,而不是每次新建%w 或回避它,会在排查生产问题时多花几倍时间翻日志找根因。