errors.new仅接受固定字符串,不支持格式化或%w包装,适合定义全局静态错误;fmt.errorf支持变量插值和%w错误链,适用于需动态上下文或封装底层错误的场景。

errors.New 创建的错误为什么不能带格式化参数
errors.New 只接受一个固定字符串,本质是直接构造 error 接口的最简实现。它不支持占位符、变量拼接或类型转换——传入 fmt.Sprintf 的结果是常见 workaround,但不是 errors.New 本身的能力。
典型误用:errors.New("failed to open file: " + filename) 看似可行,但若 filename 为 nil 或非字符串类型,会直接 panic;更隐蔽的问题是丢失了原始值的类型信息(比如整数、结构体字段),无法在后续做类型断言或错误分类。
- 只用于固定、无上下文的错误,如
errors.New("io timeout") - 需要嵌入变量时,必须先格式化好再传入,且要确保所有拼接项非 nil
- 返回的错误无法用
errors.Is或errors.As做语义比较(除非你手动定义常量错误变量)
fmt.Errorf 的 %w 动词到底什么时候该用
%w 是 Go 1.13 引入的“包装错误”专用动词,核心作用是让错误链可追溯。不用 %w,只是普通字符串拼接;用了 %w,下游才能用 errors.Unwrap、errors.Is 或 errors.As 向下查找原始错误。
常见场景:底层函数返回 os.Open 错误,上层想加一层业务含义又不丢失原始错误细节。
err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to load config: %w", err) // ✅ 可展开
// return fmt.Errorf("failed to load config: %v", err) // ❌ 断链
}
- 仅当你要保留原始错误的语义和类型时才用
%w - 一个错误里最多一个
%w,多个会导致errors.Unwrap行为不确定 -
%w后面必须跟实现了error接口的值,不能是字符串或 nil
自定义错误类型比 errors.New 更适合哪些情况
当你需要错误携带额外字段(比如 HTTP 状态码、重试次数)、支持特定方法(如 Timeout())、或参与类型断言时,errors.New 和 fmt.Errorf 都不够用。
例如网络请求失败后,既要返回错误,又要告诉调用方是否该重试:
type NetworkError struct {
Msg string
Code int
}
func (e *NetworkError) Error() string { return e.Msg }
func (e *NetworkError) Timeout() bool { return e.Code == 408 }
// 使用
return &NetworkError{Msg: "request timeout", Code: 408}
- 如果错误需被其他包识别并处理(如中间件判断是否重试),必须用结构体+方法
- 避免用指针接收者返回临时结构体错误(如
&NetworkError{...}),容易被 GC 提前回收 - 导出字段要谨慎:暴露太多内部状态可能破坏封装,只导出必要判别字段
errors.Is 和 errors.As 容易忽略的边界条件
errors.Is 检查的是整个错误链中是否存在某个目标错误(通过 == 或 Is() 方法),而 errors.As 是向下查找第一个匹配类型的错误。两者都依赖 %w 构建的链,也依赖自定义错误正确实现 Is 或 As 方法。
典型陷阱:用 errors.Is(err, io.EOF) 判断,但如果上游用 fmt.Errorf("read failed: %v", io.EOF)(没用 %w),就永远返回 false。
-
errors.Is对errors.New创建的错误只做指针或字符串精确匹配,不推荐用于动态生成的错误 -
errors.As的第二个参数必须是指向接口或具体类型的指针,写成errors.As(err, &e)而不是errors.As(err, e) - 如果错误链里有多个同类型错误,
errors.As只返回第一个,不会继续找
fmt.Errorf("xxx: %w", underlying) 就够用;真正需要自定义类型或深度控制错误行为的地方,远比初学者想象得少——但一旦踩中,调试成本极高。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











