Go Error Wrapping

Go1.13 中引入了 error wraping 机制,可以实现如下功能:

  • 类比其他语言的try catch机制,可以在 error 判断时匹配特定的 error,这样就可以分层处理 error
  • 可以自定义IsAs接口,实现自定义匹配方式,比如字符串匹配或其他条件
  • 可以在自定义 error 中内嵌其他数据,然后在上层再取出

基本结构 + 关键接口

type error interface {
	Error() string
}
  • Go1.13 提供了 error 的链式结构
package errors

func Unwrap(err error) error {
	u, ok := err.(interface {
		Unwrap() error
	})
	if !ok {
		return nil
	}
	return u.Unwrap()
}
package fmt

type wrapError struct {
	msg string
	err error
}

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

func (e *wrapError) Unwrap() error {
	return e.err
}
  • 通过上面的代码,只要符合Unwrap接口,就可以不断 Unwrap 形成 error chain

  • 利用 wraped := fmt.Errorf("test %w", err)%w就可以对原始 err 进行 wrapping

  • errors.Is(wrapedErr, tarErr)可以判定tarErr是否包含在wrapedErr

    • 注意这里的第二个参数tarErr应该是一个指针的地址,这样方便为指针创建指向的对象。如果直接传 nil 指针会报错。
var tarErrorPointer *MyError
errors.As(wrapedErr, &tarErrorPointer)
  • 用上面代码可以断言并取出特定类型的 error,注意声明是是指针类型,传入第二个参数是指针的地址

用法示例

type MyError struct {
}

func (p *MyError) Error() string {
	return "origin"
}

func testErrorWrapping() {
	var originErr error = new(MyError)
	fmt.Println(originErr) // origin

	var originErr2 error = errors.New("origin2")

	// 注意这里 %w 只能用一次(靠左),第二次使用无法起到 wrap 作用
	wrapedError := fmt.Errorf("wrap %w, %v", originErr, originErr2)
	fmt.Println(wrapedError)                // wrap origin
	fmt.Println(errors.Unwrap(wrapedError)) // origin

	isErr := errors.Is(wrapedError, originErr)
	fmt.Println(isErr) // true

	var targetError *MyError
	asErr := errors.As(wrapedError, &targetError)
	fmt.Println(asErr)       // true
	fmt.Println(targetError) // origin
}