
Go 1.13+ 的 errors.Is 和 errors.As 为什么有时不生效?
因为底层错误没用 fmt.Errorf 带 %w 动词包裹,或者用了但包裹链被中间某层“截断”了(比如转成字符串再新建错误)。errors.Is 只认 %w 构建的包裹链,其他方式(如拼接、自定义 error 实现没嵌入原错误)都会断掉。
实操建议:
- 所有需要参与判断或解包的错误传递,必须用
fmt.Errorf("xxx: %w", err),不能用fmt.Errorf("xxx: %v", err) - 第三方库返回的错误如果没包裹,你又需要向上透传,得手动再包一次:
return fmt.Errorf("db query failed: %w", dbErr) - 自定义 error 类型若想支持
errors.As,要实现Unwrap() error方法并返回被包裹的错误;若想支持多层解包,每一层都得有Unwrap()
errors.Unwrap 和直接类型断言的区别在哪?
errors.Unwrap 是安全地取包裹链中**下一层**错误,它只调用一次 Unwrap() 方法(如果实现了),不递归;而类型断言 e.(MyError) 是硬匹配当前错误实例的类型,不管它是不是被包裹的、包裹了几层。
常见错误现象:写 if e, ok := err.(MyError); ok { ... },结果永远 ok 为 false —— 因为 err 其实是 fmt.Errorf("xxx: %w", myErr),外层是 *fmt.wrapError 类型,不是 MyError。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 要检查某个具体错误类型是否在包裹链中任意位置,用
errors.As(err, &target) - 要逐层查看包裹结构(比如调试时),用循环调用
errors.Unwrap,但注意别无限循环(Unwrap()返回nil就停) - 不要对未知来源的错误做盲目断言,先用
errors.As或errors.Is
自定义 error 类型怎么写才兼容标准包裹机制?
核心就两点:实现 Unwrap() error 方法返回被包裹的错误,并且在构造时确实存了它。不实现 Unwrap,errors.Is 和 errors.As 就完全看不到你包裹的内容。
示例:
type MyDBError struct {
Msg string
Code int
Err error // 存原始错误
}
func (e *MyDBError) Error() string { return e.Msg }
func (e *MyDBError) Unwrap() error { return e.Err } // ← 这行不能少
容易踩的坑:
- 字段名不是
Err没关系,但Unwrap()必须返回那个被包裹的 error 实例 - 如果
Unwrap()返回一个新错误(比如fmt.Errorf("wrapped: %v", e.Err)),包裹链就断了 —— 新错误没有Unwrap,也不指向原错误 - 导出的 error 类型建议带
Unwrap,未导出的可以省略,但一旦要参与标准判断逻辑,就必须有
为什么 errors.Is 查不到 HTTP 状态码错误?
因为 net/http 包里的大多数错误(比如 http.ErrUseLastResponse)是哨兵错误(sentinel errors),它们是变量,不是包裹结构;而像 url.Error 虽然有 Err 字段,但它实现的是 Unwrap() error,所以能被 errors.Is 向下穿透 —— 但前提是你的比较目标是它内部的 Err,而不是 url.Error 本身。
使用场景举例:你想捕获所有底层是 io.EOF 的错误,不管它经过多少层 HTTP 或 JSON 解析包装,就该用 errors.Is(err, io.EOF);但如果直接比 errors.Is(err, url.Error{...}),永远 false —— 哨兵错误不能这么比。
实操建议:
- 哨兵错误(如
io.EOF、sql.ErrNoRows)只能作为errors.Is的第二个参数,不能作为第一个 -
url.Error、json.SyntaxError这类带Unwrap()的错误,可以用errors.Is向下查其内部错误,但不能用它自己当目标去“被 Is” - HTTP 请求失败通常返回
*url.Error,它的Err字段可能是net.OpError,再往下可能是syscall.Errno,整条链都支持Is和As
%w,断掉一层 Unwrap,整个链就失效了。实际项目里最常出问题的,不是你写的那几行,而是你依赖的 SDK 里某个错误处理偷偷把 %w 换成了 %v。










