概述
Golang中error和创建error源码解析
- 一.初识error
- 1.什么是error
- 2.error源码
- 二.error创建
- 1.errors.New()函数
- 2.fmt.Errorf()函数
Golang中的错误处理和Java,Python有很大不同,没有
try...catch
语句来处理错误。因此,
Golang中的错误处理是一个比较有争议的点,如何优雅正确的处理错误是值得去深究的。
今天先记录error
是什么及如何创建error
,撸一撸源码。
一.初识error
1.什么是error
error
错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中 。
而异常指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。
可见,错误是业务过程的一部分,而异常不是 。
Golang中的错误也是一种类型。错误用内置的error
类型表示。就像其他类型,如int
,float64
等。
错误值可以存储在变量中,也可以从函数中返回,等等。
2.error源码
在 src/builtin/builtin.go
文件下,定义了错误类型,源码如下:
// src/builtin/builtin.go
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
error
是一个接口类型,它包含一个 Error()
方法,返回值为string
。任何实现这个接口的类型都可以作为一个错误使用,Error这个方法提供了对错误的描述。
注意:error为nil
代表没有错误。
先看一个文件打开错误的例子:
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println("open failed, err:", err)
return
}
fmt.Println("file is :", f)
输出:
open failed, err: open /test.txt: The system cannot find the file specified.
可以看到输出了具体错误,分别为: 操作open
,操作对象/test.txt
,错误原因The system cannot find the file specified.
当执行打印错误语句时, fmt 包会自动调用 err.Error()
函数来打印字符串。
这就是错误描述是如何在一行中打印出来的原因。
了解了error是什么,我们接下来了解error的创建。
二.error创建
创建方式有两种:
- errors.New()
- fmt.Errorf()
1.errors.New()函数
在src/errors/errors.go
文件下,定义了 errors.New()
函数,入参为字符串,返回一个error对象:
// 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
}
New()函数返回一个错误,该错误的格式为给定的文本。
即使文本相同,每次对New的调用也会返回一个不同的错误值。
其中 errorString
是一个结构体,只有一个string
类型的字段s,并且实现了唯一的方法:Error()
我们实战一下:
// 1.errors.New() 创建一个 error
err1 := errors.New("这是 errors.New() 创建的错误")
fmt.Printf("err1 错误类型:%T,错误为:%vn", err1, err1)
输出:
err1 错误类型:*errors.errorString,错误为:这是 errors.New() 创建的错误
可以看到,错误类型是 errorString
指针,前面的errors.
表明了其在errors包下。
通常这就够了,它能反映当时“出错了”,但是有些时候我们需要更加具体的信息。即需要具体的“上下文”信息,表明具体的错误值。
这就用到了fmt.Errorf
函数
2.fmt.Errorf()函数
fmt.Errorf()
函数,它先将字符串格式化,并增加上下文的信息,更精确的描述错误。
我们先实战一下,看看和上一节的内容有什么不同:
// 2.fmt.Errorf()
err2 := fmt.Errorf("这个 fmt.Errorf() 创建的错误,错误编码为:%d", 404)
fmt.Printf("err2 错误类型:%T,错误为:%vn", err2, err2)
输出:
err2 错误类型:*errors.errorString,错误为:这个 fmt.Errorf() 创建的错误,错误编码为:404
可以看到err2
的类型是*errors.errorString
,并且错误编码 404 也输出了。。
为什么err2
返回的错误类型也是 :*errors.errorString
,我们不是用 fmt.Errorf()
创建的吗?
我们先看下其源码实现:
// 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
}
通过源码可以看到,p.wrappedErr
为 nil
的时候,会调用errors.New()
来创建错误。
所以 err2
的错误类型是*errors.errorString
这个问题就解答了。
不过又出现了新问题,这个p.wrappedErr
是什么东东呢?什么时候为nil
?
我们先看个例子:
// 3. go 1.13 新增加的错误处理特性
%w
err3 := fmt.Errorf("err3: %w", err2)
// err3包裹err2错误
fmt.Printf("err3 错误类型:%T,错误为:%vn", err3, err3)
输出:
err3 错误类型:*fmt.wrapError,错误为:err3: 这个 fmt.Errorf() 创建的错误,错误编码为:404
注意:在格式化字符串的时候,有一个 %w
占位符,表示格式化的内容是一个error
类型。
我们主要看下err3
的内容,其包裹了err2
错误信息,如下:
err3: 这个 fmt.Errorf() 创建的错误,错误编码为:404
还有一点要注意的是,err3
这次是一个 *fmt.wrapError
类型?这个类型又是源自哪里?怎么会有这样一个类型?又出现了一个新的问题…
好了,带着这些问题,我们从头开始捋一捋源码,就知道它们到底是什么?
我们注意到fmt.Errorf()
函数第一行 p := newPrinter()
创建了一个 p对象,这个p对象其实就是pp
结构体指针的实例, newPrinter()
源码如下:
// src/fmt/print.go
// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
newPrinter()
函数返回一个 pp
结构体指针。
我们看下这个结构体,并看看p.wrappedErr
字段在该结构体中定义:
// src/fmt/print.go
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
...
...
// wrapErrs is set when the format string may contain a %w verb.
wrapErrs bool
// wrappedErr records the target of the %w verb.
wrappedErr error
}
由于pp
结构体的字段较多,我们主要看两个字段:
wrapErrs
字段,bool类型,当格式字符串包含%w
动词时,将赋值为truewrappedErr
字段,error类型,记录%w
动词的目标,即例子的err2
所以我们解决了第一问题:p.wrappedErr
到底是什么,什么时候为nil
。
即:p.wrappedErr
是 pp 结构体的一个字段,当格式化错误字符串中没有%w
动词时,其为nil
。
还有第二个问题, *fmt.wrapError
类型源自哪里?
其实根源就在else
语句中,当p.wrappedErr
不为nil
时,执行以下语句:
err = &wrapError{s, p.wrappedErr}
err
是结构体wrapError
的实例,其初始化了两个字段,并且是引用取值(前面有&
)。我们来看看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
}
wrapError
结构体有两个字段:
- msg ,
string
类型 - err,
error
类型
实现了两个方法:
- Error(),也说明
wrapError
结构体实现了error
接口,是一个error
类型 - Unwrap(),作用是返回原错误值,没有自定义的
msg
了。也就是说拆开了一个被包装的 error。
所以我们的第二个问题, *fmt.wrapError
是什么,就彻底解答了。
至此,捋完fmt.Errorf()
的源码了,我们了解了想要的内容,至于p.doPrintf(format, a)
的具体实现内容很复杂,所以就没去深挖了。
总结一下吧,Golang中创建错误有两种方式:
第一种:errors.New()
函数,其返回值类型为 *errors.errorString
。
第二种:fmt.Errorf()
函数
当使用fmt.Errorf()
来创建错误时,核心有以下两点:
-
错误描述中不包含
%w
时,p.wrappedErr
为nil
,所以底层也是调用errors.New()
创建错误。因此错误类型就是*errors.errorString
。 -
错误描述中包含
%w
时,p.wrappedErr
不为nil
,所以底层实例化wrapError
结构体指针。 因此错误类型是*fmt.wrapError
,可以理解为包裹错误类型。
最后
以上就是魁梧羽毛为你收集整理的Golang学习——error和创建error源码解析一.初识error二.error创建的全部内容,希望文章能够帮你解决Golang学习——error和创建error源码解析一.初识error二.error创建所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复