
Go 1.13 引入 errors.Is 和 errors.As 等新工具支持错误包装,推荐使用 errors.Is(err, targetErr) 替代 == 直接比较,以安全、可靠地判断错误是否为特定值(包括被包装的错误)。
go 1.13 引入 `errors.is` 和 `errors.as` 等新工具支持错误包装,推荐使用 `errors.is(err, targeterr)` 替代 `==` 直接比较,以安全、可靠地判断错误是否为特定值(包括被包装的错误)。
在 Go 中,直接用 == 比较两个 error 值(如 err == errors.New("Token is expired"))几乎总是错误的。原因在于:errors.New() 每次调用都会创建一个全新的错误实例,即使内容相同,其内存地址也不同,因此 == 判断必然失败。更关键的是,现代 Go(1.13+)广泛使用错误包装(error wrapping)——例如通过 fmt.Errorf("failed to validate: %w", err) 将原始错误嵌套封装。此时原始错误仍存在,但已不再是顶层错误值,== 完全无法穿透包装进行匹配。
✅ 正确做法:使用 errors.Is 进行语义化比较
errors.Is(err, target) 会递归调用 Unwrap() 方法,逐层检查错误链中是否存在与 target 相等的底层错误(支持 == 或 Is() 方法),从而可靠识别被包装的错误:
import "errors"
var ErrTokenExpired = errors.New("Token is expired")
// ✅ 推荐:兼容包装、语义清晰、可维护
if errors.Is(err, ErrTokenExpired) {
log.Printf("Unauthorized: %s", err)
}
⚠️ 注意事项:
- 始终定义包级变量错误(如 var ErrTokenExpired = errors.New("...")),而非在比较时临时创建 errors.New();
- errors.Is 是 Go 标准库原生支持,无需额外依赖;
- 对于自定义错误类型(如实现了 Unwrap() error 或 Is(error) bool 的结构体),errors.Is 同样适用;
- 若需提取错误详情(如获取 *jwt.ValidationError),应使用 errors.As(err, &target) 而非类型断言。
❌ 反模式示例(务必避免):
// ❌ 错误:每次新建实例,永远不相等
if err == errors.New("Token is expired") { ... }
// ❌ 错误:忽略包装,无法匹配 fmt.Errorf("auth failed: %w", ErrTokenExpired)
if err == ErrTokenExpired { ... }
? 最佳实践总结:
- 在包中预先声明有意义的错误变量(var ErrTokenExpired = errors.New(...));
- 所有错误判断统一使用 errors.Is(err, YourPackage.ErrXXX);
- 包装错误时优先使用 fmt.Errorf("%w", originalErr);
- 需要类型信息时,用 errors.As(err, &target) 安全提取。
这一模式不仅提升健壮性,也使错误处理逻辑更清晰、可测试,并与 Go 生态主流实践保持一致。











