Go Error 嵌套到底是怎么实现的?
原文链接: Go Error 嵌套到底是怎么实现的?
Go Error 的设计哲学是 「Errors Are Values」。
这句话应该怎么理解呢?翻译起来挺难的。不过从源码的角度来看,好像更容易理解其背后的含义。
Go Error 源码很简单,寥寥几行:
// src/builtin/builtin.go
type error interface {
Error() string
}
error
是一个接口类型,只需要实现 Error()
方法即可。在 Error()
方法中,就可以返回自定义结构体的任意内容。
下面首先说说如何创建 error
。
创建 Error
创建 error
有两种方式,分别是:
errors.New()
;fmt.Errorf()
。
errors.New()
errors.New()
的使用延续了 Go 的一贯风格,New
一下就可以了。
举一个例子:
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("这是 errors.New() 创建的错误")
fmt.Printf("err 错误类型:%T,错误为:%v\n", err, err)
}
/* 输出
err 错误类型:*errors.errorString,错误为:这是 errors.New() 创建的错误
*/
这段代码唯一让人困惑的地方可能就是错误类型了,但没关系。只要看一下源码,就瞬间迎刃而解。
源码如下:
// src/errors/errors.go
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
可以看到,errorString
是一个结构体,实现了 Error()
方法,New
函数直接返回 errorString
指针。
这种用法很简单,但不实用。假如我还想返回程序的上下文信息,它就没辙了。
下面看第二种方式。
fmt.Errorf()
还是先看一个例子:
package main
import (
"database/sql"
"fmt"
)
func foo() error {
return sql.ErrNoRows
}
func bar() error {
return foo()
}
func main() {
err := bar()
if err == sql.ErrNoRows {
fmt.Printf("data not found, %+v\n", err)
return
}
if err != nil {
fmt.Println("Unknown error")
}
}
/* 输出
data not found, sql: no rows in result set
*/
这个例子输出了我们想要的结果,但是还不够。
一般情况下,我们会通过使用 fmt.Errorf()
函数,附加上我们想添加的文本信息,使返回内容更明确,处理起来更灵活。
所以,foo()
函数会改成下面这样:
func foo() error {
return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}
这时问题就出现了,经过 fmt.Errorf()
的封装,原始 error
类型发生了改变,这就导致 err == sql.ErrNoRows
不再成立,返回信息变成了 Unknown error
。
如果想根据返回的 error
类型做不同处理,就无法实现了。
因此,Go 1.13 为我们提供了 wrapError
来处理这个问题。
Wrap Error
看一个例子:
package main
import (
"fmt"
)
type myError struct{}
func (e myError) Error() string {
return "Error happended"
}
func main() {
e1 := myError{}
e2 := fmt.Errorf("E2: %w", e1)
e3 := fmt.Errorf("E3: %w", e2)
fmt.Println(e2)
fmt.Println(e3)
}
/* output
E2: Error happended
E3: E2: Error happended
*/
乍一看好像好没什么区别,但背后的实现原理却并不相同。
Go 扩展了 fmt.Errorf()
函数,增加了一个 %w
标识符来创建 wrapError
。
// src/fmt/errors.go
func Errorf(format string, a ...interface{}) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
当使用 w%
时,函数会返回 &wrapError{s, p.wrappedErr}
,wrapError
结构体定义如下:
// src/fmt/errors.go
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
实现了 Error()
方法,说明它是一个 error
,而 Unwrap()
方法是为了获取被封装的 error
。
// src/errors/wrap.go
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
它们之间的关系是这样的:
因此,我们可以使用 w%
将上文中的程序进行改造,使其内容输出更丰富。
如下:
package main
import (
"database/sql"
"errors"
"fmt"
)
func bar() error {
if err := foo(); err != nil {
return fmt.Errorf("bar failed: %w", foo())
}
return nil
}
func foo() error {
return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
}
func main() {
err := bar()
if errors.Is(err, sql.ErrNoRows) {
fmt.Printf("data not found, %+v\n", err)
return
}
if err != nil {
fmt.Println("Unknown error")
}
}
/* output
data not found, bar failed: foo failed: sql: no rows in result set
*/
终于有了让人满意的输出结果,每个函数都增加了必要的上下文信息,而且也符合对错误类型的判断。
errors.Is()
函数用来判断 err
以及其封装的 error
链中是否包含目标类型。这也就解决了上文提出的无法判断错误类型的问题。
后记
其实,Go 目前对 Error 的处理方式也是充满争议的。不过,官方团队正在积极和社区交流,提出改进方法。相信在不久的将来,一定会找到更好的解决方案。
现阶段来说,大部分团队可能会选择 github.com/pkg/errors
包来进行错误处理。如果感兴趣的话,可以学学看。
好了,本文就到这里吧。关注我,带你通过问题读 Go 源码。
源码地址:
推荐阅读:
参考文章:
- https://chasecs.github.io/posts/the-philosophy-of-go-error-handling/
- https://medium.com/@dche423/golang-error-handling-best-practice-cn-42982bd72672
- https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html
Go Error 嵌套到底是怎么实现的?的更多相关文章
- 读 Go 源码,可以试试这个工具
原文链接: 读 Go 源码,可以试试这个工具 编程发展至今,从面向过程到面向对象,再到现在的面向框架.写代码变成了一件越来越容易的事情. 学习基础语法,看看框架文档,几天时间搞出一个小项目并不是一件很 ...
- Go语言(golang)新发布的1.13中的Error Wrapping深度分析
Go 1.13发布的功能还有一个值得深入研究的,就是对Error的增强,也是今天我们要分析的 Error Wrapping. 背景 做Go语言开发的,肯定经常用error,但是我们也知道error非常 ...
- [Effective JavaScript 笔记]第24条:使用变量保存arguments对象
迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...
- socket+mysql
测试windows 下 Socket+Mysql调用. 环境: OS:Win7; DBMS:Mysql; IDE:codeblockS. server: #include <WINSOCK2. ...
- winSocket数据传输
服务器端: #include <WINSOCK2.H> #include <stdio.h> #pragma comment(lib,"ws2_32.lib" ...
- Oracle学习系列1-7
Oracle学习系列1 两个服务必须启动: OracleOraDb10g*TNListener 和 OracleService*** 使用sqlplusw先进行环境的设置 set linesize 3 ...
- Oracle学习系列7
Oracle学习系列7 ************************************************************************************ 关联表 ...
- c++有默认参数的函数---4
原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 1.默认参数的目的 C++可以给函数定义默认参数值.通常,调用函数时,要为函数的每个参数给定对应的实参. ...
- winsock2之最简单的win socket编程
原文:winsock2之最简单的win socket编程 server.cpp #include <WINSOCK2.H> #include <stdio.h> #pragma ...
随机推荐
- 转:Java IO
转自:http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html [案例1]创建一个新文件 ? 1 2 3 4 5 6 7 8 ...
- 3、回溯算法解题套路框架——Go语言版
前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...
- 2、动态规划接替套路框架——Go语言版
前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...
- 自动化集成:Pipeline流水语法详解
前言:该系列文章,围绕持续集成:Jenkins+Docker+K8S相关组件,实现自动化管理源码编译.打包.镜像构建.部署等操作:本篇文章主要描述Pipeline流水线用法. 一.Webhook原理 ...
- CF749B Parallelogram is Back 题解
Content 给出平行四边形的三个顶点 \((x_1,y_1),(x_2,y_2),(x_3,y_3)\),求出所有可能的第四个顶点. 数据范围:\(\forall i\in[1,3],-1000\ ...
- CF1473B String LCM 题解
Content 如果一个字符串 \(s\) 由若干个字符串 \(t\) 拼接而成,则我们说 \(s\) 能被 \(t\) 整除.定义 \(s_1,s_2\) 的最短公倍串为可以同时被 \(s_1,s_ ...
- LuoguP2097 资料分发1 题解
Content 有一些电脑,一部分电脑有双向数据线连接.如果一个电脑得到数据,它可以传送到的电脑都可以得到数据.现在,你有这个数据,问你至少将其输入几台电脑,才能使所有电脑得到数据. 数据范围:\(n ...
- CHARACTERIZING ADVERSARIAL SUBSPACES USING LOCAL INTRINSIC DIMENSIONALITY
目录 概 主要内容 LID LID估计 算法 实验 1 2 3 4 5 Ma X, Li B, Wang Y, et al. Characterizing Adversarial Subspaces ...
- [数据结构]严蔚敏版(C数据结构)配套实现程序111例
以下为根据严蔚敏版数据结构的示例和概念实现的程序 目录 一.严蔚敏版(C数据结构)配套实现程序111例 1.数组与字符串 2.栈与队列 3.链表LinkList 4.树与二叉树 5.排序相关算法 6. ...
- [算法笔记-题解]问题 B: 例题4-2 比较交换实数值
问题 B: 例题4-2 比较交换实数值 [命题人 : 外部导入] 时间限制 : 1.000 sec 内存限制 : 12 MB 题目描述 从键盘输入2个实数,按照代数值由小到大的顺序输出这两个数. 输入 ...