Golang中errors的Unwrap简析

日常在使用golang中的error类型,一般有几种使用方式,分别是使用errors.New来创建

errors.New("new error")

如果需要添加额外的信息,或者包一层调用某个函数返回的err,可以使用fmt.Errorf,例如

if err := CallSomeFunc();err != nil{
    return fmt.Errorf("err with fmt %v", err)
}

又或者可以自定义Error类型,查看Error类型的实现方式

type error interface {
    Error() string
}

可以知道只要实现Error()方法即可,所以自定义类型也可以实现error,例如

type UserError struct {
    User string
    Err  error
}

func (e *UserError) Error() string {
    return fmt.Sprintf("user : %s , err: %v", e.User, e.Err)
}

在自定义的时候就可以直接创建自定义类型即可

err := &UserError{User: "walker", Err: errors.New("no exists")}

上述也就是使用error的几个方式,但是在使用过程不乏有痛点,也就是又想保存上下文信息,让信息更全面,又想在调用外层根据不同错误类型来采取不同的处理,一般我们在内部声明了每个固定的错误,例如:

var UserNotExistsError = errors.New("user not exists")

而在外层调用想根据某个返回来判定,即可使用

if e,ok := err.(*UserError);ok && e.Err == UserNotExistsError{
    // user component error because of the user not exists
}

有点复杂,如果还包括了其他package调用的错误可能会更难受,但在Go1.13版本中新增了error的一些特性。
简单介绍下,第一个为Unwrap方法

func (e *UserError) Unwrap() error { return e.Err }

这个就是暴露内部的Err,而配合使用的还有IsAs两个方法

if errors.Is(err,UserNotExistsError){
    // user not exists
}

官方解释Is等同于err == UserNotExistsError
As相当于类型判定,例如

var e *UserNotExistsError
if errors.As(err, &e) {

}

因为有了Unwrap之后,error可能嵌套了很多层,所以上述两个方法IsAs本身就支持嵌套的查询,内部实现可以查看下源码,也是一层层揭开,去做对比判定。

据此Unwrap特性也提供了一个格式化标记%w,所以可以这样使用

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("user %v: %w", user, err)
}

参考:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

17 + 14 =