我是靠谱客的博主 魁梧羽毛,这篇文章主要介绍Golang学习——error和创建error源码解析一.初识error二.error创建,现在分享给大家,希望可以做个参考。

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类型表示。就像其他类型,如intfloat64等。

错误值可以存储在变量中,也可以从函数中返回,等等。

2.error源码

src/builtin/builtin.go 文件下,定义了错误类型,源码如下:

复制代码
1
2
3
4
5
6
7
// 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代表没有错误

先看一个文件打开错误的例子:

复制代码
1
2
3
4
5
6
7
f, err := os.Open("/test.txt") if err != nil { fmt.Println("open failed, err:", err) return } fmt.Println("file is :", f)

输出:

复制代码
1
2
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对象:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
2
3
4
// 1.errors.New() 创建一个 error err1 := errors.New("这是 errors.New() 创建的错误") fmt.Printf("err1 错误类型:%T,错误为:%vn", err1, err1)

输出:

复制代码
1
2
err1 错误类型:*errors.errorString,错误为:这是 errors.New() 创建的错误

可以看到,错误类型是 errorString指针,前面的errors.表明了其在errors包下。

通常这就够了,它能反映当时“出错了”,但是有些时候我们需要更加具体的信息。即需要具体的“上下文”信息,表明具体的错误值。

这就用到了fmt.Errorf 函数

2.fmt.Errorf()函数

fmt.Errorf()函数,它先将字符串格式化,并增加上下文的信息,更精确的描述错误。

我们先实战一下,看看和上一节的内容有什么不同:

复制代码
1
2
3
4
// 2.fmt.Errorf() err2 := fmt.Errorf("这个 fmt.Errorf() 创建的错误,错误编码为:%d", 404) fmt.Printf("err2 错误类型:%T,错误为:%vn", err2, err2)

输出:

复制代码
1
2
err2 错误类型:*errors.errorString,错误为:这个 fmt.Errorf() 创建的错误,错误编码为:404

可以看到err2的类型是*errors.errorString,并且错误编码 404 也输出了。。

为什么err2返回的错误类型也是 :*errors.errorString,我们不是用 fmt.Errorf()创建的吗?

我们先看下其源码实现:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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.wrappedErrnil的时候,会调用errors.New()来创建错误。

所以 err2的错误类型是*errors.errorString这个问题就解答了。

不过又出现了新问题,这个p.wrappedErr是什么东东呢?什么时候为nil?

我们先看个例子:

复制代码
1
2
3
4
5
6
// 3. go 1.13 新增加的错误处理特性 %w err3 := fmt.Errorf("err3: %w", err2) // err3包裹err2错误 fmt.Printf("err3 错误类型:%T,错误为:%vn", err3, err3)

输出:

复制代码
1
2
err3 错误类型:*fmt.wrapError,错误为:err3: 这个 fmt.Errorf() 创建的错误,错误编码为:404

注意:在格式化字符串的时候,有一个 %w占位符,表示格式化的内容是一个error类型。

我们主要看下err3的内容,其包裹了err2错误信息,如下:

复制代码
1
2
err3: 这个 fmt.Errorf() 创建的错误,错误编码为:404

还有一点要注意的是,err3这次是一个 *fmt.wrapError类型?这个类型又是源自哪里?怎么会有这样一个类型?又出现了一个新的问题…

好了,带着这些问题,我们从头开始捋一捋源码,就知道它们到底是什么?

我们注意到fmt.Errorf()函数第一行 p := newPrinter()创建了一个 p对象,这个p对象其实就是pp结构体指针的实例, newPrinter()源码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
// 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字段在该结构体中定义:

复制代码
1
2
3
4
5
6
7
8
9
10
11
// 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动词时,将赋值为true
  • wrappedErr字段,error类型,记录%w动词的目标,即例子的err2

所以我们解决了第一问题:p.wrappedErr到底是什么,什么时候为nil

即:p.wrappedErr是 pp 结构体的一个字段,当格式化错误字符串中没有%w动词时,其为nil

还有第二个问题, *fmt.wrapError类型源自哪里?

其实根源就在else语句中,当p.wrappedErr不为nil时,执行以下语句:

复制代码
1
2
err = &wrapError{s, p.wrappedErr}

err 是结构体wrapError的实例,其初始化了两个字段,并且是引用取值(前面有&)。我们来看看wrapError源码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
// 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()来创建错误时,核心有以下两点:

  1. 错误描述中不包含 %w时,p.wrappedErrnil,所以底层也是调用errors.New()创建错误。因此错误类型就是*errors.errorString

  2. 错误描述中包含%w时,p.wrappedErr不为nil,所以底层实例化wrapError结构体指针。 因此错误类型是*fmt.wrapError,可以理解为包裹错误类型。

最后

以上就是魁梧羽毛最近收集整理的关于Golang学习——error和创建error源码解析一.初识error二.error创建的全部内容,更多相关Golang学习——error和创建error源码解析一内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(91)

评论列表共有 0 条评论

立即
投稿
返回
顶部