Go errors.Is 怎样判断包装错误是否属于目标错误
来源:17golang原创
时间:2026-10-20 19:46:04 208浏览 收藏
项目里经常会给底层错误补充业务上下文,例如把“文件不存在”改成“读取配置失败:文件不存在”。如果上层继续用 err == target,包装之后就可能判断失败。真正要问的是:目标错误是否仍在当前错误树里,而不是两段错误文本是否相同。
官方资料:https://pkg.go.dev/errors#Is
用fmt.Errorf("...: %w", err)保留可遍历的错误链,再用errors.Is(err, target)判断目标错误;直接相等只适合你明确需要同一个错误值的场景。
errors.New每次创建的错误值都不同,文本相同不代表值相等。%w会保留包装关系,%v只负责格式化文本。- 自定义
Is只应表达当前错误与目标错误的浅层等价关系。
先看清直接比较失败的原因
下面的代码有两个容易混淆的事实:两次调用 errors.New 得到的是两个不同的错误值;而 fmt.Errorf 返回的又是一个新的包装错误。即使最终打印出来的文本相似,指针和值也未必相同。
package main
import (
"errors"
"fmt"
)
var ErrMissing = errors.New("配置项不存在") // 哨兵错误:整个包复用同一个值
func loadConfig() error {
return fmt.Errorf("读取服务配置失败: %w", ErrMissing) // %w 保留底层错误关系
}
func main() {
err := loadConfig()
fresh := errors.New("配置项不存在") // 文本相同,但不是 ErrMissing
fmt.Println(err == ErrMissing) // 包装错误与底层错误不是同一个值
fmt.Println(err == fresh) // 新建错误值也不会因为文本相同而相等
fmt.Println(errors.Is(err, ErrMissing)) // 沿错误链检查哨兵错误
}
这段代码里,前两个比较表达式都不适合表达“底层原因是否为配置缺失”。errors.Is 会先检查外层错误,再沿 Unwrap 关系寻找目标,因此它能识别被上下文包住的 ErrMissing。

用 %w 建立能被检查的错误树
在边界层补充上下文时,%w 是关键。它让返回值同时具备可读的描述和可遍历的底层错误;如果使用 %v,字符串仍然能打印出来,但错误链不会因为这次格式化自动建立。
func readSettings() error {
if err := openSettings(); err != nil {
return fmt.Errorf("初始化设置: %w", err) // 保留下层原因,方便调用方判断
}
return nil
}
func openSettings() error {
return ErrMissing // 返回稳定的哨兵错误,调用方可用 errors.Is 识别
}
func handle() error {
err := readSettings()
if errors.Is(err, ErrMissing) { // 判断原因,不依赖错误文本
return fmt.Errorf("请先创建设置文件: %w", err) // 继续补充上下文
}
return err
}
错误链可以有多层,调用方不需要知道中间经过了多少层包装。需要注意的是,只有确实希望上层依赖底层错误语义时才使用 %w;一旦暴露了某个哨兵错误,后续替换实现时就要考虑兼容这项可观察行为。
按错误树理解 errors.Is 的匹配范围
官方文档把错误树定义为当前错误以及通过 Unwrap() error 或 Unwrap() []error 得到的子错误。errors.Is 会先看当前节点,再按深度优先方式遍历子节点;因此它处理的不只是单层 fmt.Errorf。
func combine() error {
first := fmt.Errorf("读取用户配置: %w", ErrMissing) // 第一条分支保留缺失原因
second := errors.New("网络不可用") // 第二条分支代表另一种原因
return errors.Join(first, second) // 把多个原因组成一棵错误树
}
func check() {
err := combine()
fmt.Println(errors.Is(err, ErrMissing)) // 第一分支命中
fmt.Println(errors.Is(err, errors.New("网络不可用"))) // 新值文本相同仍不会命中
}
errors.Join 中的每个非空错误都可以被 errors.Is 检查,但目标错误本身必须是可比较的。对于需要提取具体类型或字段的情况,应考虑 errors.As 或较新的 errors.AsType,不要把“是否属于某个类型”和“是否匹配某个错误值”混成一个判断。

哨兵错误与自定义 Is 方法怎样选
固定语义适合用包级哨兵错误,例如 ErrMissing、ErrPermission。调用方只关心“是不是这一类原因”,就用 errors.Is;调用方还需要路径、操作名等字段,就改用 errors.As 提取具体错误类型。
type quotaError struct {
Limit int
}
func (e quotaError) Error() string {
return fmt.Sprintf("超过配额 %d", e.Limit)
}
func (e quotaError) Is(target error) bool {
_, ok := target.(quotaError) // 只表达同一错误类别的浅层匹配
return ok
}
func checkQuota(err error) bool {
return errors.Is(err, quotaError{}) // 不比较具体 Limit,避免把字段语义误当成类别
}
自定义 Is 不应递归调用 Unwrap,也不应把所有错误都判成匹配。它负责当前类型和目标之间的浅层等价关系,错误树的遍历仍由 errors.Is 负责。若目标需要携带数据,采用自定义类型加 errors.As 更直接。
常见问题
错误消息一样,为什么 errors.Is 还是 false?
errors.New 每次返回新的错误值。要稳定匹配,请声明并复用同一个哨兵错误,或为错误类型实现清晰的 Is 方法。
%v 能不能代替 %w?
如果上层只需要展示文本可以使用 %v;如果调用方还要识别底层原因,必须保留 %w 形成错误链。
errors.Is 能不能拿来判断错误类型?
它主要匹配目标错误值或目标错误定义的 Is 规则。需要读取具体类型字段时,使用 errors.As 或 errors.AsType。
排查包装错误时,可以固定成三步:先确认目标是错误值还是错误类型,再检查边界是否使用了 %w,最后用 errors.Is 遍历错误树。这样既保留了上下文,也不会把字符串格式当成错误语义。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
492 收藏
-
280 收藏
-
439 收藏
-
346 收藏
-
141 收藏
-
427 收藏
-
440 收藏
-
491 收藏
-
349 收藏
-
107 收藏
-
328 收藏
-
280 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习