Errors.Is用于判断错误是否等于哨兵错误(如io.EOF),errors.As用于提取并类型断言底层错误值,自定义错误需实现Unwrap方法才能被二者正确处理。

用 errors.Is 判断是否为某个具体错误
go 1.13 引入了 errors.Is,它能穿透多层包装(比如 fmt.Errorf("wrap: %w", err)),安全判断一个错误是否「等于」某个目标错误。比直接用 == 更可靠,尤其在错误被多次包装后。
常见误用是拿 errors.Is(err, io.EOF) 去判断自定义错误——但 io.EOF 是一个变量,不是类型;而你自己的错误如果没显式包装它,errors.Is 就会返回 false。
- 只对已知的、导出的哨兵错误(如
io.EOF、os.ErrNotExist)用errors.Is - 若错误来自第三方库,先查文档确认它是否暴露了哨兵变量,而不是自己 new 一个同类错误去比
-
errors.Is不适用于判断「是不是某个自定义错误类型」,那是errors.As的事
用 errors.As 提取底层错误值并做类型断言
当你需要访问错误内部字段(比如 http 状态码、数据库错误码),就得把错误还原成原始类型。errors.As 正是为此设计:它会沿着错误链向上查找,找到第一个匹配目标类型的错误值,并赋值给输出参数。
典型陷阱是传入非指针变量,比如写成 errors.As(err, &myErr) 是对的,但写成 errors.As(err, myErr)(没取地址)会导致编译失败或静默失败。
立即学习“go语言免费学习笔记(深入)”;
- 目标变量必须是指针类型:
var e *MyCustomError,然后调用errors.As(err, &e) - 如果错误链里有多个同类型错误,
errors.As只返回最内层那个(即最早出现的) - 别和
err.(*MyCustomError)混用——后者只检查当前错误本身,不穿透包装,且 panic 风险高
自定义错误类型时,要实现 Unwrap 才能被 errors.Is/errors.As 支持
如果你的错误结构体封装了另一个错误(比如加了上下文日志),又想让它兼容标准错误处理链,就必须显式实现 Unwrap() error 方法。否则 errors.Is 和 errors.As 都无法深入到它包裹的底层错误。
例如:
type MyError struct { Msg string Err error } func (e *MyError) Error() string { return e.Msg } func (e *MyError) Unwrap() error { return e.Err } // ← 必须有这一行
- 没有
Unwrap,errors.As(err, &target)在err是*MyError时永远失败 - 如果错误可能嵌套多层,确保每一层都实现了
Unwrap,否则链会在某一层断掉 - 注意不要循环引用:
Unwrap()返回自身或形成环,会导致errors.Is无限递归 panic
区分哨兵错误、自定义错误类型和字符串匹配的适用场景
很多人一遇到错误就下意识用 strings.Contains(err.Error(), "timeout"),这很脆弱:翻译、日志前缀、格式微调都会让匹配失效。应按优先级选择判断方式:
- 优先用
errors.Is(err, xxx)——适用于标准库或明确提供哨兵变量的包 - 其次用
errors.As(err, &e)——适用于需读取错误字段的自定义类型 - 最后才考虑
err.Error()字符串匹配——仅限调试、日志采样等非关键路径,且要加注释说明为什么不能用更健壮的方式 - 避免在生产逻辑中用
reflect.typeof(err).Name()判断类型,它绕过了错误链,也不支持接口实现
真正容易被忽略的是:即使你用了 errors.As,如果目标类型没导出字段,或者字段是私有的,你还是取不到有用信息——这时候得回头检查错误定义是否合理,而不是硬塞反射。