errors 包


在 Go 1.13 之前,errors包只公开了New函数,从 Go 1.13 之后,增加了IsAs、与Unwrap函数。

Is函数是用于取代==判断错误的场合,例如以下的程序片段:

if err == io.EOF {
    ...
}

可以改用Is函数:

if errors.Is(err, io.EOF) {
    ...
}

Is也可以用于判断nilerr若有实现Is方法,也可以使用Is函数来判断,因为Is函数的源码是这么实现的:

func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()
    for {
        if isComparable && err == target {
            return true
        }
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        // TODO: consider supporing target.Is(err). This would allow
        // user-definable predicates, but also may allow for coping with sloppy
        // APIs, thereby making it easier to get away with them.
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

(从源码中的注解可以看到,未来可能进一步支持target实现Is方法的情况。)

As函数是用于取代类型断言判断错误类型的场合,例如以下的程序片段:

if e, ok := err.(*PathError); ok {
    ...
}

可以改用As函数:

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

来看看As函数的实现:

func As(err error, target interface{}) bool {
    if target == nil {
        panic("errors: target cannot be nil")
    }
    val := reflectlite.ValueOf(target)
    typ := val.Type()
    if typ.Kind() != reflectlite.Ptr || val.IsNil() {
        panic("errors: target must be a non-nil pointer")
    }
    if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {
        panic("errors: *target must be interface or implement error")
    }
    targetType := typ.Elem()
    for err != nil {
        if reflectlite.TypeOf(err).AssignableTo(targetType) {
            val.Elem().Set(reflectlite.ValueOf(err))
            return true
        }
        if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
            return true
        }
        err = Unwrap(err)
    }
    return false
}

target若不是指针就会panic;另外,err可以是个实现As方法的实例。

IsAs的实现中,都看到了Unwrap函数:

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

从 Go 1.13 开始,错误可以实现Unwrap方法,如果e1.Unwrap()可以得到e2,那么e1实例包裹了e2,因此,对于需要包含根源错误的情况,保存根源错误的字段不需要是公开的,可以透过Unwrap来返回,Unwrap为获取包裹的错误提供了统一的名称。

fmt包有个Errorf函数,可以格式化字符串并返回error实例,在 Go 1.13 之前的版本,就只是将格式化后的字符串传给errors.New;从 Go 1.13 开始,Errorf支持%w,这会令返回的error实例会包裹指定的错误,并具有Unwrap方法。


展开阅读全文