当前位置:首页 > 文章列表 > Golang > Go问答 > Go errors.Is 怎样判断包装错误是否属于目标错误

Go errors.Is 怎样判断包装错误是否属于目标错误

来源:17golang原创 2026-10-20 19:46:04 0浏览 收藏

项目里经常会给底层错误补充业务上下文,例如把“文件不存在”改成“读取配置失败:文件不存在”。如果上层继续用 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。

Go errors.New、fmt.Errorf百分号w和errors.Is组成错误树的静态关系说明图
图1:错误包装结构说明图,展示外层上下文、%w 连接和 errors.Is 目标匹配的关系;这是静态说明图,不是运行截图。

用 %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,不要把“是否属于某个类型”和“是否匹配某个错误值”混成一个判断。

Go errors.Is在单错误链和多错误树中按目标值匹配的静态边界图
图2:errors.Is 匹配边界说明图,展示单链、多分支错误树、目标错误和类型提取之间的静态关系;这是结构图,不是运行证据。

哨兵错误与自定义 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 遍历错误树。这样既保留了上下文,也不会把字符串格式当成错误语义。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go range nil channel 永远阻塞怎样定位Go range nil channel 永远阻塞怎样定位
上一篇
Go range nil channel 永远阻塞怎样定位
Go netip.AddrFromSlice 处理 IPv4 映射 IPv6 的转换边界
下一篇
Go netip.AddrFromSlice 处理 IPv4 映射 IPv6 的转换边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    568次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    614次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    641次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    601次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    418次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码