errors.Is不能判断自定义错误底层类型,因其仅通过调用错误链中各错误的Is方法判断语义相等,不进行类型断言或反射;若自定义错误未实现Is方法,则errors.Is恒返回false。

errors.Is 为什么不能判断自定义错误的底层类型
errors.Is 只检查错误链中是否存在「语义相等」的错误,即调用 Is(error) 方法返回 true 的错误。它不关心底层结构体类型,也不做类型断言或反射比对。
常见误解是以为 errors.Is(err, myErr) 能识别你定义的 *MyError 类型 —— 实际上,除非你显式实现了 Is 方法并让它返回 true,否则它永远返回 false。
-
标准库中的
os.ErrNotExist、io.EOF等能被errors.Is识别,是因为它们本身实现了Is方法 - 用
fmt.Errorf("xxx: %w", err)包装后的错误,仍保留原错误的Is行为(前提是被包装的err支持) - 直接用
errors.New("xxx")或fmt.Errorf("xxx")创建的错误,不支持Is比较,只能靠errors.As或类型断言
什么时候该用 errors.As 而不是 errors.Is
当你需要获取错误的具体值(比如访问字段、调用方法),而不是只判断「是否等于某个哨兵错误」时,errors.As 是唯一选择。
例如:你定义了带状态码和消息的错误类型 *HTTPError,想取出它的 Code 字段 —— 这必须用 errors.As 提取指针,errors.Is 完全无能为力。
立即学习“go语言免费学习笔记(深入)”;
-
errors.Is返回bool,适合 if/else 分支逻辑(如「如果文件不存在就创建」) -
errors.As返回bool+ 填充目标变量,适合需进一步操作错误值的场景 - 若错误链里有多个同类型错误,
errors.As找到第一个匹配的并停止;不会继续向上遍历
var httpErr *HTTPError
if errors.As(err, &httpErr) {
log.Printf("HTTP error code: %d", httpErr.Code)
}
自定义错误实现 Is 方法的最小必要写法
要让自己的错误能被 errors.Is 正确识别,必须在错误类型上实现 Is(target error) bool 方法,并确保逻辑可传递、无环、不依赖地址比较。
- 不要用
==直接比较接收者和target(地址不同就失败) - 推荐方式:用
errors.As尝试将target转成你的类型,再做字段级比较(如错误码、字符串消息) - 若你的错误只作为哨兵(如
ErrTimeout),可直接返回target == ErrTimeout,但必须保证它是包级变量且永不重分配
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string { return e.Msg }
func (e *MyError) Is(target error) bool {
var t *MyError
if errors.As(target, &t) {
return e.Code == t.Code
}
return false
}
errors.Is 在多层包装下的行为边界
errors.Is 会沿着 %w 包装链逐层调用 Unwrap(),直到遇到 nil 或某层返回 true。但它不会进入非标准包装(如自定义 WrappedError 但没实现 Unwrap)。
- 如果中间某层错误的
Unwrap()返回nil,链就断了,后续错误不可达 - 如果某层
Unwrap()返回自身(造成循环),errors.Is会 panic(Go 1.20+ 加了检测) - 用
fmt.Errorf("wrap: %w", err)是安全的;但手动实现Unwrap时务必确保返回的是另一个错误,不是自己
真正容易被忽略的是:即使你用了 %w,如果被包装的错误本身不支持 Is(比如纯字符串错误),那整条链对 errors.Is 来说仍是“不可识别”的 —— 它不会自动比对错误消息字符串。










