Go errors.Is 为什么不会反向调用目标值的 Is
来源:17golang原创
时间:2026-09-27 22:21:47 301浏览 收藏
errors.Is(err, target) 不会反向调用 target.Is(err),因为它的契约是搜索第一个参数 err 的错误树。对树中的每个当前节点,标准库先判断它是否等于 target,再看这个当前节点是否实现 Is(error) bool 并调用 current.Is(target)。target 只是匹配目标,不是另一个需要遍历的错误树。
官方文档:https://pkg.go.dev/errors#Is
- 匹配方向固定为
err到target,不是双向协议。 - 自定义
Is应写在会出现在错误树里的具体错误类型上。 Is方法只做浅比较,不应在方法内部调用Unwrap。
errors.Is 的方向由第一个参数决定
我第一次写自定义错误匹配时,把 Is 放到了“目标错误”类型上,直觉是目标应该决定谁能匹配自己。结果方法完全没有触发。重新看官方定义后才发现,errors.Is 从来不是对两个值做对称协商:它把 err 当作待搜索的树,把 target 当作查询条件。
检查顺序可以概括为:当前错误与目标直接相等;若不相等,则调用当前错误自己的 Is(target);仍不匹配时,再按照 Unwrap() error 或 Unwrap() []error 展开子节点。多子节点按先序深度优先检查。

反向实现 Is 为什么没有效果
下面的目标类型即使把 Is 写成永远返回 true,也不会让普通源错误匹配成功,因为 GreedyTarget.Is 不在调用路径上:
type GreedyTarget struct{}
func (*GreedyTarget) Error() string { return "target" }
func (*GreedyTarget) Is(error) bool {
// 这个方法位于 target 一侧,errors.Is 不会反向调用它
return true
}
source := errors.New("source")
target := &GreedyTarget{}
// 结果仍为 false:source 不等于 target,也没有 source.Is(target)
matched := errors.Is(source, target)
这不是遗漏,而是 API 的方向性。若目标值也能主动决定匹配,错误树里的节点和查询目标会同时影响语义,调用方很难判断由谁负责兼容。标准库把责任放在被检查的错误节点上,因此一个错误类型可以声明“我等价于某个公共哨兵错误”。
把自定义 Is 写在产生错误的一侧
更常见的做法是让业务错误根据稳定字段匹配目标。下面的 CodeError 出现在返回链中,所以它的 Is 会被调用:
type CodeError struct {
Code string
Err error
}
func (e *CodeError) Error() string { return e.Code }
func (e *CodeError) Unwrap() error {
// 包装原因交给 errors.Is 统一遍历
return e.Err
}
func (e *CodeError) Is(target error) bool {
// 只比较当前节点和目标,不在这里递归展开错误链
t, ok := target.(*CodeError)
return ok && e.Code == t.Code
}
var ErrTimeout = &CodeError{Code: "timeout"}
func loadProfile() error {
cause := &CodeError{Code: "timeout"}
return fmt.Errorf("load profile: %w", cause)
}
err := loadProfile()
// 指针不相等,但错误树中的 CodeError.Is 会按 Code 匹配
if errors.Is(err, ErrTimeout) {
fmt.Println("可以按超时策略处理")
}

如果只需要匹配一个固定哨兵,也可以直接返回 target == ErrTimeout。若目标带字段,字段必须代表稳定兼容语义;不要把易变文案、时间或堆栈字符串当成匹配条件。
包装树与边界条件
| 位置 | errors.Is 的处理 |
|---|---|
| 当前节点 | 先做直接相等,再尝试当前节点的 Is(target) |
| target | 作为比较参数,不调用它的 Is,并应为可比较值 |
| 单个包装 | 通过 Unwrap() error 继续检查 |
| 多错误包装 | 通过 Unwrap() []error 深度优先检查子树 |
自定义 Is 返回 false 不会终止整棵树的搜索,标准库仍会继续展开该节点。也正因为遍历由外层统一完成,官方要求 Is 只做浅比较,不要自行调用两侧的 Unwrap。这样包装、连接错误和自定义等价关系才能组合,而不会重复递归。
交换 errors.Is 的两个参数可以吗?
通常不可以。交换后被搜索的错误树也变了,原错误链不会再被遍历,自定义方法的接收者也会改变。
target 必须是哨兵错误吗?
不必须,但它应是可比较且语义稳定的错误值。常见做法是包级哨兵,或只携带稳定匹配字段的目标对象。
自定义 Is 能同时保留底层原因吗?
可以。类型同时实现浅层 Is 和 Unwrap,前者表达等价关系,后者把底层原因交给标准库继续遍历。
-
364 收藏
-
458 收藏
-
421 收藏
-
261 收藏
-
330 收藏
-
357 收藏
-
225 收藏
-
301 收藏
-
389 收藏
-
225 收藏
-
240 收藏
-
497 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习