Errors.Is 比 == 更可靠,因为它会沿错误链逐层调用 Unwrap() 匹配底层错误,而 == 仅比较最外层;Errors.As 则用于类型断言以访问错误内部字段。

Errors.Is 为什么比 == 更可靠
因为 Go 的错误可能是包装过的,直接用 == 只能比对最外层错误,而 Errors.Is 会顺着错误链(error chain)逐层调用 Unwrap(),直到找到匹配的底层错误。比如 os.Open 返回的 *os.PathError 被 fmt.Errorf("failed to open: %w", err) 包装后,err == os.ErrNotExist 是 false,但 Errors.Is(err, os.ErrNotExist) 是 true。
常见错误现象:if err == os.ErrNotExist 在加了 %w 包装后永远不成立;日志里看到 “no such file or directory”,但判断分支没进。
- 只对实现了
Unwrap() error方法的错误才有效(标准库错误基本都支持) - 目标值必须是“错误变量”或“错误指针”,不能是字符串或临时
errors.New("xxx")(除非你确保它和链中某一层完全相等) - 如果错误链里有多个
os.ErrNotExist,Errors.Is仍只返回 true,不关心层级深度
什么时候该用 Errors.As 而不是 Errors.Is
Errors.Is 判断“是否等于某个错误值”,Errors.As 判断“是否可以转换为某个错误类型”,用于需要访问错误内部字段的场景。比如你想拿到 *os.PathError 里的 Path 字段,或者自定义错误的 Code() 方法。
使用场景:记录具体出错路径、根据错误结构做分类重试、暴露 HTTP 状态码。
立即学习“go语言免费学习笔记(深入)”;
-
Errors.As(err, &pathErr)成功后,pathErr就指向链中第一个匹配的*os.PathError - 务必传入指针(
&var),否则As无法赋值 - 如果错误链里存在多个不同类型的包装器(比如先 wrap 再 fmt.Errorf("%w", …)),
As仍只取第一个能转型成功的 - 不要用
As去匹配接口(如error),它只认具体类型或指针类型
自定义错误配合 Is/As 的正确写法
如果你自己定义错误类型并希望它能被 Errors.Is 和 Errors.As 正确识别,核心是实现 Unwrap() 和(可选)Is() 或 As() 方法。但多数情况下,只实现 Unwrap() 就够了——只要你的错误包装了另一个错误,Is/As 就能穿透过去。
容易踩的坑:忘记返回被包装的错误,或返回 nil 导致链中断;在 Is() 里硬编码比较逻辑反而破坏标准行为。
- 基础包装写法:
type MyError struct { Msg string; Err error }<br>func (e *MyError) Error() string { return e.Msg }<br>func (e *MyError) Unwrap() error { return e.Err } - 如果想让
Is对某个特定值返回 true(比如让Errors.Is(err, ErrTimeout)生效),可以额外实现Is(target error) bool方法,但仅限于你明确控制的目标错误变量 - 避免在
Unwrap()中返回新错误(如fmt.Errorf("wrap: %w", e.Err)),这会创建新链,可能绕过原意图
性能和兼容性要注意什么
Errors.Is 和 Errors.As 都是线性遍历错误链,最坏情况是 O(n),但实际中错误链通常很短(2~4 层),开销可忽略。真正影响性能的是频繁调用 + 深链,比如在 tight loop 里反复判断同一个错误。
兼容性方面:这两个函数从 Go 1.13 引入,低于该版本会编译失败;若需兼容老版本,可用 github.com/pkg/errors 的 Is()/As(),但注意其行为与标准库不完全一致(比如对 nil 的处理)。
- Go 1.13+ 标准库的
Is对nil错误返回 false,即使 target 也是 nil —— 所以别传nil当 target - 不要在
defer里依赖Errors.Is判断 panic 后的 error(panic 不产生 error 链),那是 recover 的事 - 测试时若用
errors.New构造测试错误,记得它不可被As转成具体类型,只能靠Is或字符串匹配
Unwrap(),判断时按需选 Is 还是 As,其余都是链式穿透的自然结果。










