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 ...
随机推荐
- ciscn_2019_s_9
很简单的一道题 例行检查 没有开启nx保护,就想到了shellcode来做 很明显的栈溢出 唯一的要求就是shellcode长度不能超过0x24 通过jump跳转到shellcode的位置 完整exp ...
- CF19A World Football Cup 题解
Content 有 \(n\) 个球队参加一场足球比赛,比赛排名前 \(\dfrac{n}{2}\) 的队伍将会进入下一轮的淘汰赛.比赛将会打 \(\dfrac{n(n-1)}{2}\) 场,胜者得 ...
- 『学了就忘』Linux日志管理 — 91、日志服务rsyslogd说明
目录 1.日志文件格式 2.rsyslogd服务的配置文件 (1)rsyslog.conf文件内容 (2)rsyslog.conf配文件内容说明 (3)定义自己的日志 1.日志文件格式 只要是由日志服 ...
- Linux下使用JDK11部署Nacos启动报错:Could not find or load main class
Linux下使用JDK11部署Nacos 错误日志 /nacos/jdk-11.0.12/bin/java -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize ...
- Linux(centos)使用docker安装pdf2htmlEX
pdf2htmlEX是一款可以将pdf文档转换成html文件的插件,但是Linux系统安装起来很麻烦,所以我们使用docker进行安装 首先要安装docker 因为国外镜像很慢,所以我们这边修改使用国 ...
- windows使用natapp教程
这种免费的 有个缺点 过段时间就会连接不上,需要不定时更换域名地址 Linux系统参考:https://www.cnblogs.com/pxblog/p/10549847.html 官网地址:http ...
- html5调用摄像头截图
关于html5调用音视频等多媒体硬件的API已经很成熟,不过一直找不到机会把这些硬件转化为实际的应用场景,不过近年来随着iot和AI的浪潮,我觉得软硬结合的时机已经成熟.那我们就提前熟悉下怎么操作这些 ...
- c++设计模式概述之工厂
类写的不规范,原因: 缩短篇幅,实际中请不要这样写. 欢迎指正 工厂模式,如其名,想象下现实生活中的工厂,比如Apple的组装工厂,小米产品的组装工厂,华为设备的组装工厂.对我们用户而言,不需要知道他 ...
- 【LeetCode】133. Clone Graph 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS BFS 日期 题目地址:https://le ...
- 【LeetCode】738. Monotone Increasing Digits 解题报告(Python)
[LeetCode]738. Monotone Increasing Digits 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu ...