
Go 语言中不能直接用 == 比较任意错误值,必须用 errors.Is 或自定义可比较的错误类型 —— 这是 Go 错误处理的核心约束,不是风格问题,而是类型系统决定的。
为什么 err == ErrNotFound 有时失效
因为 Go 的 error 是接口,底层可能是不同指针、不同结构体实例,甚至同一错误文本构造多次也会产生不相等的值。比如 fmt.Errorf("not found") 每次都新建一个匿名结构体,地址不同,== 必然为 false。
常见错误现象:if err == ErrNotFound 在单元测试里通过,上线后突然不生效 —— 很可能是因为调用链中某处用了 fmt.Errorf("wrap: %w", err),把原错误包了一层,导致指针变了。
- 只有导出的、包级变量错误(如
io.EOF)才适合用==直接比较 - 所有用
fmt.Errorf、errors.New动态构造的错误,都不该依赖== - 如果错误需要携带字段(如 HTTP 状态码),必须用结构体 + 实现
Error()方法,且避免在Error()返回中拼接动态内容(影响可比性)
定义全局错误变量的标准写法(带值比较能力)
最稳妥的方式是定义包级导出变量,类型为 *MyError 或 MyError(非指针需实现 error 接口且支持值比较)。
立即学习“go语言免费学习笔记(深入)”;
推荐结构体定义(支持字段扩展,也便于后续加 Unwrap()):
type NotFoundError struct {
Resource string
}
func (e *NotFoundError) Error() string {
return "not found: " + e.Resource
}
func (e *NotFoundError) Is(target error) bool {
_, ok := target.(*NotFoundError)
return ok
}
var ErrNotFound = &NotFoundError{Resource: "unknown"}
- 用指针类型
*NotFoundError定义变量,确保全局唯一实例 - 实现
Is()方法,让errors.Is(err, ErrNotFound)可靠工作 - 不要在
Error()中调用fmt.Sprintf或访问外部状态(如时间、随机数),否则影响可比性和测试稳定性
什么时候该用 errors.Is 而不是 ==
几乎所有涉及错误判断的生产代码都应该优先用 errors.Is,尤其当错误可能被包装(%w)、重试、或来自第三方库时。
典型使用场景:
- HTTP handler 中判断是否要返回 404:
if errors.Is(err, ErrNotFound) - 数据库操作后检查约束冲突:
if errors.Is(err, sql.ErrNoRows) - 重试逻辑中忽略特定临时错误:
if !errors.Is(err, ErrTransient)
注意:errors.Is 会递归调用 Unwrap(),所以如果你的错误类型实现了 Unwrap(),要确保它返回的是“下一层”错误,而不是自己 —— 否则会无限循环 panic。
容易被忽略的兼容性细节
Go 1.13 引入 errors.Is / As,但很多老项目仍跑在 Go 1.12 或更低版本。如果必须兼容,别直接用 errors.Is,改用反射或字符串匹配(不推荐),或者升级 Go 版本。
另一个坑:自定义错误类型如果实现了 Unwrap() 但没实现 Is(),errors.Is 会 fallback 到默认行为(只比指针),导致你写了结构体却没获得结构体字段比较能力。
还有:不要给错误变量赋 nil 值(如 var ErrNotFound error = nil),这会让 errors.Is(err, ErrNotFound) 对所有 nil 错误都返回 true,彻底失去区分度。










