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 ...
随机推荐
- CF140D New Year Contest 题解
Content 小 G 想打一场跨年比赛,比赛从下午 \(18:00\) 开始一直持续到次日清晨 \(6:00\),一共有 \(n\) 道题目.小 G 在比赛开始之前需要花费 10 分钟考虑这些题目的 ...
- 大学MOOC课程视频下载、流文件合并、批量重命名、b站视频下载及学习课程视频推荐
计算机行业技术更新快,编程语言种类多,在当今大数据和人工智能的时代,为了能在相关领域有所成就,就必须掌握好python.R等语言,较好的数学基础和深入的行业背景知识.计算机从业人员务必践行" ...
- List集合取其中指定几条数据
newList= oldList.subList(start, end); start,end分别是第几个到第几个,截取的内容包含前不包含结尾,用下标索引 此方法会改变原始list列表,返回的这个子列 ...
- 【LeetCode】281. Zigzag Iterator 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 deque 日期 题目地址:https://leetc ...
- 【LeetCode】252. Meeting Rooms 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 排序 日期 题目地址:https://leetcode ...
- 【LeetCode】1022. Smallest Integer Divisible by K 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- 【LeetCode】1004. Max Consecutive Ones III 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 虫取法/双指针 日期 题目地址:https://le ...
- 【九度OJ】题目1207:质因数的个数 解题报告
[九度OJ]题目1207:质因数的个数 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1207 题目描述: 求正整数N(N& ...
- 【LeetCode】743. Network Delay Time 解题报告(Python)
[LeetCode]743. Network Delay Time 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: ht ...
- 【剑指Offer】复杂链表的复制 解题报告(Python)
[剑指Offer]复杂链表的复制 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interviews 题 ...