Golangerrors.Is与errors.As区别详解
一分耕耘,一分收获!既然打开了这篇文章《Golang errors.Is与errors.As区别解析》,就坚持看下去吧!文中内容包含等等知识点...希望你能在阅读本文后,能真真实实学到知识或者帮你解决心中的疑惑,也欢迎大佬或者新人朋友们多留言评论,多给建议!谢谢!
errors.Is用于判断错误链中是否存在指定的错误值,errors.As用于查找并提取错误链中特定类型的错误。errors.Is通过递归解包比较错误值,适用于检查哨兵错误;errors.As通过类型断言和反射提取错误详情,适用于获取结构体错误信息。二者分别适用于身份判断与数据提取场景。

Golang中的errors.Is和errors.As,说白了,都是用来检查和处理错误链的,但它们的侧重点和使用场景却大相径庭。简单来说,errors.Is关注的是“这个错误是不是我关心的那个特定错误值”,它做的是值的比较;而errors.As则关注“这个错误链里有没有某个特定类型的错误,并且我需要把那个错误的值取出来用”,它做的是类型断言。

解决方案
理解errors.Is和errors.As的关键在于它们如何遍历错误链以及它们各自的目标。

errors.Is的设计初衷是为了解决Go 1.13之前错误比对的痛点,尤其是当错误被层层包裹(wrapped)之后。它会递归地检查错误链,直到找到一个与目标错误值相等的错误,或者链条的末端。这对于检查预定义的“哨兵错误”(sentinel errors),比如io.EOF,或者你自己定义的ErrNotFound这类错误非常有用。你不需要知道错误被包裹了多少层,errors.Is会帮你找到。
package main
import (
"errors"
"fmt"
"os"
)
var ErrMyCustom = errors.New("这是一个自定义错误")
func doSomething() error {
// 模拟一个被包裹的错误
return fmt.Errorf("操作失败: %w", ErrMyCustom)
}
func main() {
err := doSomething()
// 使用 errors.Is 检查是否是 ErrMyCustom
if errors.Is(err, ErrMyCustom) {
fmt.Println("检测到自定义错误 ErrMyCustom")
} else {
fmt.Println("不是 ErrMyCustom:", err)
}
// 模拟文件不存在的错误
_, err = os.Open("non_existent_file.txt")
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在错误")
} else {
fmt.Println("其他文件错误:", err)
}
}
}而errors.As则更进一步,它不仅能检查错误链中是否存在某种特定类型的错误,还能将找到的第一个匹配类型的错误“解包”并赋值给一个目标变量。这在你需要从自定义错误类型中提取额外信息时显得尤为强大。例如,你可能有一个包含错误码或详细描述的结构体错误,errors.As能帮你把这个结构体找出来并赋值,然后你就可以访问它的字段了。

package main
import (
"errors"
"fmt"
)
// 定义一个自定义错误类型,包含错误码和消息
type MyDetailedError struct {
Code int
Message string
}
func (e *MyDetailedError) Error() string {
return fmt.Sprintf("错误码: %d, 详情: %s", e.Code, e.Message)
}
func doAnotherThing(shouldFail bool) error {
if shouldFail {
// 返回一个被包裹的自定义详细错误
return fmt.Errorf("业务逻辑失败: %w", &MyDetailedError{Code: 500, Message: "数据库连接超时"})
}
return nil
}
func main() {
err := doAnotherThing(true)
var detailedErr *MyDetailedError
if errors.As(err, &detailedErr) {
fmt.Printf("成功提取详细错误:Code=%d, Message='%s'\n", detailedErr.Code, detailedErr.Message)
if detailedErr.Code == 500 {
fmt.Println("这是一个服务器内部错误。")
}
} else {
fmt.Println("没有找到 MyDetailedError 类型错误:", err)
}
err = doAnotherThing(false)
if errors.As(err, &detailedErr) {
fmt.Println("不应该走到这里,没有错误发生。")
} else {
fmt.Println("没有错误,自然也找不到 MyDetailedError。")
}
}errors.Is的内部机制:为什么它能识别错误链?
errors.Is之所以能识别错误链,关键在于它利用了Go语言中错误包装(error wrapping)的约定。当一个错误e通过fmt.Errorf("... %w", err)或实现了Unwrap() error方法的错误类型被包裹时,errors.Is就能顺着这个链条向上(或者说向内)查找。
我们来看一下errors.Is的核心逻辑(简化版,并非完整源码,但体现了思想):
// 概念性伪代码,不是实际源码
func Is(err, target error) bool {
if err == nil || target == nil {
return err == target // 允许 nil 比较
}
// 1. 直接比较:如果当前错误值与目标错误值相等,直接返回 true
if err == target {
return true
}
// 2. 如果当前错误实现了 Is(error) bool 方法,调用它
// 这允许自定义错误类型定义自己的“相等”逻辑
if x, ok := err.(interface{ Is(error) bool }); ok {
if x.Is(target) {
return true
}
}
// 3. 递归解包:如果当前错误实现了 Unwrap() error 方法,继续解包并递归调用 Is
if unwrapErr, ok := err.(interface{ Unwrap() error }); ok {
return Is(unwrapErr.Unwrap(), target) // 递归调用
}
// 如果以上条件都不满足,说明链条末端或不匹配
return false
}从这段伪代码中,我们可以看到errors.Is的几个关键点:
- 直接比较:最直接的,如果
err本身就是target,那当然是true。 - 自定义
Is方法:这是Go 1.13引入的一个非常灵活的机制。如果一个错误类型实现了Is(error) bool方法,那么errors.Is会优先调用这个方法来判断相等性。这允许你为复杂的错误类型定义更精细的相等逻辑,例如,你可能认为两个MyError实例只要错误码相同就“是”同一种错误,而不管消息内容。 - 递归
Unwrap:这是errors.Is能够遍历错误链的核心。它会不断地调用Unwrap()方法,将包裹的错误一层层剥开,直到没有可解包的错误为止,并在每层都执行上述的比较和Is方法检查。
这种设计使得errors.Is成为检查错误“身份”的理想工具,无论这个身份被包裹了多少层。它避免了过去手动遍历Unwrap链的繁琐和易错。
errors.As的深层解析:类型断言与错误处理的灵活性
errors.As的功能比errors.Is要强大一些,它不仅仅是判断“是不是”,更是要“取出那个是的东西”。它的核心是类型断言,并且它能将匹配到的错误实例赋值给一个目标变量,让你能进一步操作这个错误。
errors.As的内部实现会涉及Go的反射机制,因为它需要在运行时检查错误链中的每个错误是否可以被“断言”成目标类型,并将值设置给目标变量。
// 概念性伪代码,不是实际源码,但揭示了反射的介入
func As(err error, target interface{}) bool {
if err == nil || target == nil {
return false // 目标不能是 nil
}
// target 必须是一个指向接口或结构体的指针
targetVal := reflect.ValueOf(target)
if targetVal.Kind() != reflect.Ptr || targetVal.IsNil() {
panic("errors.As: target must be a non-nil pointer")
}
targetType := targetVal.Elem().Type() // 获取目标指针指向的类型
// 开始遍历错误链
for {
// 1. 直接类型断言:尝试将当前 err 断言为目标类型
// 注意:这里的断言是 Go 语言内置的类型断言,而不是反射的 CanConvert/Convert
// 它会尝试将 err 转换为 targetType
if reflect.TypeOf(err).AssignableTo(targetType) {
// 如果可以赋值,并且目标是一个指针,则将 err 的值设置给 targetVal 指向的位置
// 这一步是反射的核心,将找到的错误值“注入”到用户提供的变量中
targetVal.Elem().Set(reflect.ValueOf(err))
return true
}
// 2. 如果当前错误实现了 As(interface{}) bool 方法,调用它
// 允许自定义错误类型定义自己的 As 逻辑
if x, ok := err.(interface{ As(interface{}) bool }); ok {
if x.As(target) {
return true
}
}
// 3. 递归解包:如果当前错误实现了 Unwrap() error 方法,继续解包
if unwrapErr, ok := err.(interface{ Unwrap() error }); ok {
err = unwrapErr.Unwrap()
if err == nil { // 解包到 nil,链条结束
break
}
continue // 继续下一轮循环检查解包后的错误
}
// 链条末端,没有找到匹配类型
break
}
return false
}errors.As的重点在于:
- 目标必须是指针:
errors.As的第二个参数target必须是一个指向接口类型或具体结构体类型的非空指针。这是因为errors.As需要将找到的错误值“写回”到这个指针所指向的位置。 - 类型匹配与赋值:它会遍历错误链,对每个错误实例,尝试判断它是否可以赋值给
target所指向的类型。一旦找到匹配的,它就会通过反射将这个错误实例赋值给target,然后返回true。 - 自定义
As方法:类似于Is,错误类型也可以实现As(interface{}) bool方法,来提供更复杂的类型匹配和赋值逻辑。 - 递归
Unwrap:同样,它也会利用Unwrap()方法来遍历错误链。
errors.As的强大之处在于,它使得错误处理从简单的“是或否”判断,升级到了“是,并且给我它的具体内容”的层面。这在需要根据错误类型执行不同业务逻辑,或者需要从错误中提取特定数据(如HTTP状态码、业务错误码、数据库错误详情)时,显得尤为重要。
选择适合的场景:何时用Is,何时用As?
理解了它们的原理,选择起来就简单多了:
使用
errors.Is的场景:- 判断错误身份:当你只关心一个错误是否是某个特定的、预定义的错误值时。例如,判断一个文件操作错误是否是
os.ErrNotExist,或者一个网络操作错误是否是io.EOF。 - 检查哨兵错误:你定义了一些全局的
errors.New("...")常量作为错误类型标识,只想知道错误链中是否存在这个标识。 - 简单条件分支:你只需要根据错误的“种类”来决定程序的流程,而不需要获取错误内部的任何数据。
- 性能考虑(微小):
errors.Is通常比errors.As性能稍好,因为它不涉及反射(除非自定义Is方法内部用了反射,但通常不会)。对于性能敏感的场景,如果Is能满足需求,优先考虑它。
举例:
if errors.Is(err, os.ErrPermission) { fmt.Println("权限不足,无法操作。") }- 判断错误身份:当你只关心一个错误是否是某个特定的、预定义的错误值时。例如,判断一个文件操作错误是否是
使用
errors.As的场景:- 提取错误详情:当你定义了包含额外信息的自定义错误类型(通常是结构体),并且需要从错误中获取这些信息来做进一步处理时。比如,一个
DatabaseError可能包含SQLState和ErrorCode字段,你需要提取它们来做更精细的错误日志或用户提示。 - 类型特定处理:你需要根据错误链中某个特定类型的错误来执行不同的逻辑。例如,如果发现是
*ValidationError,就返回HTTP 400;如果是*AuthenticationError,就返回HTTP 401。 - 更灵活的错误匹配:当你的错误类型是接口,或者你需要根据接口实现来判断错误时,
errors.As能帮你把符合接口的错误实例提取出来。
举例:
var dbErr *DatabaseError // 假设 DatabaseError 是一个自定义结构体错误 if errors.As(err, &dbErr) { fmt.Printf("数据库错误:Code=%s, Message=%s\n", dbErr.SQLState, dbErr.Message) // 根据 dbErr.SQLState 做进一步处理 } else { fmt.Println("非数据库错误,或无法解析。") }- 提取错误详情:当你定义了包含额外信息的自定义错误类型(通常是结构体),并且需要从错误中获取这些信息来做进一步处理时。比如,一个
总的来说,errors.Is是你的“身份识别器”,告诉你“是不是这个错误”;errors.As则是你的“内容提取器”,告诉你“是这个错误,并且这是它的具体内容”。两者互为补充,构成了Go语言强大而灵活的错误处理体系。在实际开发中,你会发现它们都是不可或缺的工具。
到这里,我们也就讲完了《Golangerrors.Is与errors.As区别详解》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!
WebAssembly加速图像处理算法实现
- 上一篇
- WebAssembly加速图像处理算法实现
- 下一篇
- 《三角符文》1-4隐藏BOSS攻略详解
-
- Golang · Go教程 | 14分钟前 |
- GolangCI/CD测试流程实现详解
- 347浏览 收藏
-
- Golang · Go教程 | 19分钟前 |
- Golang模块冲突解决全攻略
- 200浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Go语言处理JSON浮点数编码技巧
- 391浏览 收藏
-
- Golang · Go教程 | 49分钟前 |
- Golangselect多路复用实战教程详解
- 307浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- MGO存储嵌套结构体方法全解析
- 119浏览 收藏
-
- Golang · Go教程 | 9小时前 | 格式化输出 printf fmt库 格式化动词 Stringer接口
- Golangfmt库用法与格式化技巧解析
- 140浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang配置Protobuf安装教程
- 147浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang中介者模式实现与通信解耦技巧
- 378浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3167次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3380次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3409次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4513次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3789次使用
-
- Golangmap实践及实现原理解析
- 2022-12-28 505浏览
-
- go和golang的区别解析:帮你选择合适的编程语言
- 2023-12-29 503浏览
-
- 试了下Golang实现try catch的方法
- 2022-12-27 502浏览
-
- 如何在go语言中实现高并发的服务器架构
- 2023-08-27 502浏览
-
- 提升工作效率的Go语言项目开发经验分享
- 2023-11-03 502浏览

