Golang 学习——error 和创建 error 源码解析

Golang中error和创建error源码解析

Golang中的错误处理和Java,Python有很大不同,没有try...catch语句来处理错误。因此,Golang中的错误处理是一个比较有争议的点,如何优雅正确的处理错误是值得去深究的。

今天先记录error是什么及如何创建error,撸一撸源码。

一.初识error

1.什么是error

error错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中 。

而异常指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。

可见,错误是业务过程的一部分,而异常不是 。

Golang中的错误也是一种类型。错误用内置的error类型表示。就像其他类型,如intfloat64等。

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

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,错误为:%v\n", 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,错误为:%v\n", 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.wrappedErrnil的时候,会调用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,错误为:%v\n", 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动词时,将赋值为true
  • wrappedErr字段,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()来创建错误时,核心有以下两点:

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

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

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!