Go语言的errors

article/2025/9/22 4:05:32

Go语言的errors包有4个方法:

  • errors.As
  • errors.Is
  • errors.New
  • errors.Unwrap

本期我们来揭开他们的神秘面纱。俗话说的好,柿子还得挑软的捏,按照国际惯例我们先从最简单的New函数开始。

在 Go 源码目录的 errors 目录下,有errors.gowrap.go两个文件,以及对应的errors_test.gowrap_test.go两个单元测试文件。要学一个新东西时,其实看xxx_test.go是一个非常不错的选择,它会告诉你这些函数该怎么用。说回errors.New函数,它在errors.go文件中定义,其他3个函数都在wrap.go文件中。

errors.go 文件源码如下:

package errorsfunc New(text string) error {return &errorString{text}
}type errorString struct {s string
}func (e *errorString) Error() string {return e.s
}

去掉注释就剩这点内容了,代码就不做解释了。看下Go简洁的编程哲学,errors包中定义了New函数,而没有用NewError,因为Go通过包名调用函数的特性,包命已经说明了上下文。

在这里插入图片描述


第二个我们来看errors.Unwrap函数,它的源码如下:

func Unwrap(err error) error {u, ok := err.(interface {Unwrap() error})if !ok {return nil}return u.Unwrap()
}

乍一看好像不明所以,我们把上面的代码重写一下:

type Wraper interface {Unwrap() error
}func Unwrap(err error) error {u, ok := err.(Wraper)if !ok {return nil}return u.Unwrap()
}

现在就清楚多了,首先断言err是否实现了匿名接口interface{ Unwrap() error },如果实现了这个接口就调用它的Unwrap函数,否则返回nil。通过Unwrap函数,就可以生成一条错误链。

在这里插入图片描述


再来看errors.Is函数。源码如下:

// Is用来判断错误链中是否有错误和target匹配.
// 错误链由err以及重复调用Unwrap得到的error组成。
// err和target匹配的条件是他俩相等或者err实现了Is函数并且该函数返回true.
// 通过提供Is方法,一个错误类型可以和一个现有错误等同。例如,MyError定义了如下方法:
//	func (m MyError) Is(target error) bool { return target == os.ErrExist }
// 此时 Is(MyError{}, os.ErrExist) 返回true. 参见标准库的syscall.Errno.Is.
func Is(err, target error) bool {if target == nil {return err == target}isComparable := reflectlite.TypeOf(target).Comparable()for {if isComparable && err == target {return true}if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {return true}// TODO: consider supporting target.Is(err). This would allow// user-definable predicates, but also may allow for coping with sloppy// APIs, thereby making it easier to get away with them.if err = Unwrap(err); err == nil {return false}}
}

Is函数中也有一个匿名接口interface{ Is(error) bool },逻辑还是很简单的,唯一吸引眼球的是它的反射用的是reflectlite包,而不是我们熟知的reflect包。reflectlite是一个内部使用的包,并不对外公开,基本套路和reflect大同小异,以你对reflaect包的理解套用到reflectlite包也是可以的。

在这里插入图片描述


最后看errors.As函数。源码如下:

// 寻找错误链中第一个和target匹配的error,如果能找到,将err的值设置到target并返回true,否则返回false。
// 错误链由err以及重复调用Unwrap得到的error组成。
// 如果err可以赋值给target或者err有As(实现了匿名接口)函数并且As函数返回true,则err和target是匹配的。
// 在后一种情况下,由As函数负责设置target。
// 提供了As方法的error可以视为另一种类型的错误。
// 如果target既不是error类型也不是接口类型,As函数会panic
func As(err error, target interface{}) bool {if target == nil {panic("errors: target cannot be nil")}val := reflectlite.ValueOf(target)typ := val.Type()if typ.Kind() != reflectlite.Ptr || val.IsNil() {panic("errors: target must be a non-nil pointer")}if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {panic("errors: *target must be interface or implement error")}targetType := typ.Elem()for err != nil {if reflectlite.TypeOf(err).AssignableTo(targetType) {val.Elem().Set(reflectlite.ValueOf(err))return true}if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {return true}err = Unwrap(err)}return false
}var errorType = reflectlite.TypeOf((*error)(nil)).Elem()

As 函数就要复杂一点了,不过流程还是很清晰的,和Is函数的流程一般无二。这里用到了较多的反射,我们可以看下当err和target匹配时的处理val.Elem().Set(reflectlite.ValueOf(err))

val是target反射出的ValueElem方法可以获取元素值的Value,具体来说就是接口包含的值、指针指向的值、以及容器类型(切片|map|通道)包含的值。当然reflectlite包的Elem函数只支持接口和指针,这也是它和reflect包不同的地方,也因此As方法要求target必须是指针类型。如果你打开Elem函数的实现,就能看到下面的注释:

// Elem returns the value that the interface v contains or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.

三句英文清晰明了,相信不用我再翻译了。言归正传,当err和target匹配时,将err反射出来的Value设置给了target。我们知道Value结构体有三个字段:

  • 一个rtype类型的指针,表示类型信息
  • 一个unsafe.Pointer,指向数据的指针
  • 一个flag

那么ValueSet方法干了啥呢?不妨看下源代码:

// Set assigns x to the value v.
// It panics if CanSet returns false.
// As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value) {v.mustBeAssignable()x.mustBeExported() // 防止泄露非导出字段var target unsafe.Pointerif v.kind() == Interface {target = v.ptr}x = x.assignTo("reflectlite.Set", v.typ, target)if x.flag&flagIndir != 0 {typedmemmove(v.typ, v.ptr, x.ptr)} else {*(*unsafe.Pointer)(v.ptr) = x.ptr}
}

先不要对flagIndirtypedmemmove感到疑惑,ifelse分支的目的都是一样的,那就是替换Value中那个指向数据的指针ptr

在这里插入图片描述

所以当err和target匹配时,As函数用err的值替换了target的数据,但是保留了target的类型。

最后我们再来看flagIndirtypedmemmove的问题。flagIndir是个啥在Value的源码中有说明:

type Value struct {typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data.// Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointer// flag holds metadata about the value.// The lowest bits are flag bits://	- flagIndir: val holds a pointer to the dataflag
}type flag uintptrconst (flagKindWidth        = 5 // there are 27 kindsflagKindMask    flag = 1<<flagKindWidth - 1flagStickyRO    flag = 1 << 5flagEmbedRO     flag = 1 << 6flagIndir       flag = 1 << 7flagAddr        flag = 1 << 8flagMethod      flag = 1 << 9flagMethodShift      = 10flagRO          flag = flagStickyRO | flagEmbedRO
)

flagIndir是一个标识,表示Valueptr字段究竟是指向数据还是指向指针。如果设置了flagIndir,则ptr指向指针,如果没有设置,则ptr指向数据。更多内容可以参见这篇文章。我们还可以通过下面的代码来验证这一点。

type MyValue struct {typ uint64ptr unsafe.Pointerflag
}type flag uintptrvar flagIndir flag = 1 << 7func main() {var a = 1var p = &avala := reflect.ValueOf(a)valp := reflect.ValueOf(p)myVal := (*MyValue)(unsafe.Pointer(&vala))fmt.Printf("%b\n", myVal.flag)fmt.Println(myVal.flag & flagIndir)fmt.Println("---")myVal = (*MyValue)(unsafe.Pointer(&valp))fmt.Printf("%b\n", myVal.flag)fmt.Println(myVal.flag & flagIndir)
}//===== 输出 =====//
10000010
128
---
10110
0
//===============//

所以Value.Set函数的逻辑如下:

  • 如果Value指向的是指针,那么可以直接赋值;
  • 如果Value指向的是数据,那么调用typedmemmove函数将x.ptr指向的数据拷贝到v.ptr指向的内存,拷贝多少字节由类型v.typ决定。

typedmemmove函数在源码中只有一个函数定义,应该是一个汇编函数,遗憾的是我只在runtime包找到了memmove函数的汇编实现,没找到typedmemmove的汇编实现。

// typedmemmove copies a value of type t to dst from src.
//go:noescape
func typedmemmove(t *rtype, dst, src unsafe.Pointer)

在这里插入图片描述


既然都说到这儿了,那就不得不提一嘴github.com/pkg/errors包,它也提供了 NewUnwrapIsAs 这4个函数,那么他们和标准库的函数有啥区别呢?

其实github.com/pkg/errors包的UnwrapIsAs 函数就是直接调用的标准库errors的函数。New函数有点区别,它返回的结构体除了有错误信息还有调用栈。

github.com/pkg/errors包在标准库的基础上提供了更多的函数。比如提供了Wrap函数包装错误,提供了Cause接口获取底层错误。并且它提供的错误类型是实现了CauseUnwrap接口的,不需要我们自己去定义错误类型实现接口了,此外还提供了调用栈。使用它应该能获得不错的体验,减少自己自定义错误类型。



http://chatgpt.dhexx.cn/article/uZR5jeje.shtml

相关文章

no python application found, check your startup logs for errors错误解决

今天在倒腾django项目的时候突然遇到一个bug&#xff0c;一开始报的是内部服务器错误&#xff1a;“Internal Server Error”&#xff0c;此时服务器的状态是Nginx开启了8000端口&#xff0c;uWSGI服务也在启动中&#xff0c;然后开始排查&#xff0c;首先从Nginx下手&#xff…

活动图和流程图的区别

活动图是UML用于对系统的动态行为建模的另一种常用工具&#xff0c;它描述活动的顺序&#xff0c;展现从一个活动到另一个活动的控制流。活动图在本质上是一种流程图。 活动图与流程图的区别 (1)、流程图着重描述处理过程&#xff0c;它的主要控制结构是顺序、分支和循环&#…

活动图详解

活动图 一、活动图概要 ​ ★描述系统的动态行为。 ​ ★包含活动状态(ActionState)&#xff0c;活动状态是指业务用例的一个执行步骤或一个操作&#xff0c;不是普通对象的状态。 ​ ★活动图适合描述在没有外部事件触发的情况下的系统内部的逻辑执行过程&#xff1b;否则…

UML画图之活动图

前言 前面说到活动图与状态图之间是有联系并有区别的。那么现在让我们来认识一下活动图是怎样的吧&#xff01; 活动图 what 阐明了业务的工作流程&#xff0c;业务是由很多活动构成的。 举个栗子&#xff1a;机房上机的活动流程是 登陆→验证→打开qq→发消息。。 活动图…

【UML建模】活动图(Activity Diagram)

文章目录 1.概述2.常用的节点图例2.1.开始、结束、动作节点2.2.决策、合并节点2.3.fork、join 节点2.4.泳道 3.总结 1.概述 有经验的同学一定看到过产品经理给的业务流程图&#xff0c;UML的活动图和流程图画法是很相似的&#xff0c;只是相对于流程图来说&#xff0c;活动图有…

UML--活动图详解

活动图 活动图是状态机的一个特殊例子&#xff0c;它强调计算过程中的顺序和并发步骤。活动图所有或多数状态都是活动状态或动作状态&#xff0c;所有或大部分的转换都由原状态中完成的活动触发。 活动图的含义 活动图是一种用于描述系统行为的模型视图&#xff0c;它可用来…

UML 活动图

UML 概述 UML 全称 Unified Modeling Language&#xff0c;又称统一建模语言或标准建模语言&#xff0c;是始于1997年一个 OMG 标准&#xff0c;它是一个支持模型化和软件系统开发的图形化语言&#xff0c;为软件开发的所有阶段提供模型化和可视化支持。 UML是一种定义良好、…

UML图之四——活动图

点击打开链接活动图是一种流程图&#xff0c;用来描述活动的序列&#xff0c;从一个活动到另一个活动的控制流。 活动图的作用&#xff1a;描述用例&#xff0c;描述类的操作。 活动图的构成 必要组成元素&#xff1a; 1、活动&#xff1a;命令的执行&#xff0c;活动的进行…

EA绘制活动图

目录 创建一个project新建一个包添加图创建泳道更改泳道方向添加活动 创建一个project 新建一个包 添加图 创建泳道 更改泳道方向 添加活动 画折线可以鼠标放在线处&#xff0c;按shift 按ctrl鼠标滚轮可以缩放视图

活动图的制作非常简单,只需5个步骤即可完成精美互动图!

“需求分析”,就是活动图的核心思想。 它合理的利用图像表达方式,对某个案例的执行工作及实现过程进行直观的分析呈现,当一副完整的图形展示出来的时候,会比复杂的文字描述表现更加直观且通俗易懂,可以使对方一目了然看懂该项案例的功能。并且,还可以同时说明案例分工以…

【UML】活动图(Activity Diagram)

目录&#xff1a; 1、什么是活动图 2、活动图的构成 &#xff08;1&#xff09;起点 &#xff08;2&#xff09;重点 &#xff08;3&#xff09;活动名称 &#xff08;4&#xff09;判断条件 &#xff08;5&#xff09;同步条 &#xff08;6&#xff09;接收信号 &…

【图形设计】手把手教你画活动图,再无难搞的流程分析

编辑导语&#xff1a;在工作中&#xff0c;每当遇到复杂多变的业务&#xff0c;流程冗长时&#xff0c;可以借助活动图来分解流程。作者从四个方面分析如何画活动图&#xff0c;搞定流程分析。 上次介绍了《用例图这样画&#xff0c;3步让你做需求分析有理有据》&#xff0c;这…

UML图:活动图详细介绍

活动图简介 什么是活动图&#xff08;Activity Diagram&#xff09; 活动图是UML用于对系统的动态行为建模的另一种常用工具,它描述活动的顺序&#xff0c;展现从一个活动到另一个活动的控制流,活动图在本质上是一种流程图&#xff1b;活动图着重表现从一个活动到另一个活动的…

活动图、类图、顺序图、状态图

目录 1. 活动图1.1 活动图的开始、结束、对象1.2 活动节点1.3 分支1.4 分岔和汇合&#xff08; Forking and Joining&#xff09;1.5 泳道&#xff08;Swimlanes&#xff09;1.6 活动图小结 2. 类图2.1 类图定义2.2 类图中常用的UML元素2.3 UML中类的表示2.4 类元素的命名2.5 关…

【UML】——活动图

一、活动图概述 1、流程图&#xff1a; 常被用来建立算法模型&#xff0c;使用流程图可以表示一个算法的执行序列、过程、判定点、分支和循环 活动图和流程图十分类似&#xff0c;不同之处在于它支持并行活动 活动图的缺点&#xff1a;很难清楚的描述动作与对象之间的关系&…

活动图与流程图区别以及各自画法

* 定义 一、流程图是流经一个系统的信息流、观点流或部件流的图形代表。在企业中&#xff0c;流程图主要用来说明某一过程。这种过程既可以是生产线上的工艺流程&#xff0c;也可以是完成一项任务必需的管理过程。 二、活动图(activity diagram&#xff0c;动态图)是阐明了业务…

UML——活动图

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainai.net/zhanghan 在没有接触UML图的时候我们最常画的图就是流程图&#xff0c;通过画机房收费系统的活动图发现活动图的本质上是一种流程图&#xff0c;它包括的要素有开始状态、活动、判断节点、除此之外和流程…

UML--活动图

一、 概述 活动图(Activity Diagram)是描述满足用例要求所要进行的活动以及活动间的约束关系&#xff0c;有利于识别并行活动。它对于系统的功能建模特别重要&#xff0c;强调对象间的控制流程&#xff0c;活动图在本质上是一种流程图 二、特点 -描述一个操作执行过程中所完成的…

用例图、类图、状态图、活动图、顺序图、协作图

实验二 UML建模工具 1.实验目的 &#xff08;1&#xff09;熟悉StarUML等工具软件的特色及工作环境&#xff1b; &#xff08;2&#xff09;熟悉各种UML图的含义及用途&#xff1b;掌握利用starUML等工具绘制各种UML图的方法。 2.实验内容 &#xff08;1&#xff09;上网…

UML活动图

面向对象的软件开发方法的第一步&#xff1a;业务建模<--使用活动图 转载&#xff1a;https://www.cnblogs.com/xiaolongbao-lzh/p/4591953.html 活动图概述 •活动图和交互图是UML中对系统动态方面建模的两种主要形式 •交互图强调的是对象到对象的控制流&#xff0c;而…