go error 包装

493 阅读2分钟

今天主要记录一下fmt.Errorf() 函数的用法,主要是 %w 和 %v 的区别

fmt.Errors

错误包装

首先开看一段简单的代码搞清楚生成错误的类型

func main() {

  baseErr := errors.New("base err")
	wbaseErr := fmt.Errorf("werr %w", baseErr)
	vbaseErr := fmt.Errorf("verr %v", baseErr)

	fmt.Printf("baseErr Type is %T\n", baseErr)
	fmt.Println("baseErr value = ", reflect.ValueOf(baseErr))
	fmt.Printf("wbaseErr Type is %T\n", wbaseErr)
	fmt.Println("wbaseErr value = ", reflect.ValueOf(wbaseErr))
	fmt.Printf("vbaseErr Type is %T\n", vbaseErr)
	fmt.Println("vbaseErr value = ", reflect.ValueOf(vbaseErr))

}

结果:

baseErr Type is *errors.errorString
baseErr value =  base err
wbaseErr Type is *fmt.wrapError
wbaseErr value =  werr base err
vbaseErr Type is *errors.errorString
vbaseErr value =  verr base err

可见 使用 %w%v 都是对错误添加上下文信息,但是使用%w的时候,返回的是错误的实际类型是*fmt.wrapError

type wrapError struct {
    msg string
    err error
}

使用 %v的时候,返回的类型和我们平时使用的errors.New() 返回的类型一样 *errors.errorString

error

error 是一个很简单的接口

type error interface {
    Error() string
}

下面介绍上文提到的两种比较简单的实现

errors.errorString

就是一个结构体,包括一个错误信息字段s,然后Error方法返回错误信息

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

所谓fmt.wrapError

wrap 这个单词的中文意思是 ‘
阅其源码不难发现fmt.wrapError 就是一个上下文信息和一个error的组合 , 就是将 error 裹起来并附加上下文信息 msg
Error 方法用以返回上下文信息
Unwrap 方法用于返回组合前的error

type wrapError struct {
    msg string
    err error
}

func (e *wrapError) Error() string {
    return e.msg
}

func (e *wrapError) Unwrap() error {
    return e.err
}

errors.Is

方法原型

func Is(err, target error) bool

在Is 方法里首先也会使用 == 来比较 error

image.png

但是如果使用==比较不成功 接下来 Is 方法会 Unwrap(解包裹) err 参数,并递归调用,继续Is判断

image.png

从这里也能理解了使用errors.Is 和使用 == 比较error的区别

fmt.Println(errors.Is(wbaseErr, baseErr)) // true
fmt.Println(errors.Is(vbaseErr, baseErr)) // false

说明
使用 %w生成的wbaseErrbaseErr 的包装
使用 %v生成的vbaseErr 是将baseErr 转换成了另一种错误类型

error.As

func As(err error, target any) bool

As 方法和Is 方法有相同的返回逻辑,另外,当As 方法返回true的时候,会将err 赋值给 target

func main() {

    baseErr := errors.New("base err")
    wbaseErr := fmt.Errorf("werr %w", baseErr)

    fmt.Println(baseErr == wbaseErr)                                      // false
    fmt.Println(reflect.TypeOf(baseErr), "-", reflect.ValueOf(baseErr))   // *errors.errorString - base err
    fmt.Println(reflect.TypeOf(wbaseErr), "-", reflect.ValueOf(wbaseErr)) // *fmt.wrapError - werr base err

    fmt.Println(errors.As(wbaseErr, &baseErr))                            // true
    fmt.Println(baseErr == wbaseErr)                                      // true
    fmt.Println(reflect.TypeOf(baseErr), "-", reflect.ValueOf(baseErr))   // *fmt.wrapError - werr base err
    fmt.Println(reflect.TypeOf(wbaseErr), "-", reflect.ValueOf(wbaseErr)) // *fmt.wrapError - werr base err

}