
链式error
- 新的error类型wrapError
- 增强了fmt.Errorf()以便拆解warpError
- 引入了errors.Unwrap()以拆解warpError
- 引入了errors.Is()用于检查error链条中是否包含指定的错误值
- 引入了errors.As()用于检查error链条中是否包含指定的错误类型
1.wrapError
wrapError新的error类型
type wrapError struct { msg string //存储上下文信息和err.Error() err error //存储原error } func (e *wrapError) Error() string { return e.msg } func (e *wrapError) Unwrap() error { return e.err }wrapError初看起来很像前面介绍的os.PathError,os.PathError通过os.PathError.Op和os.PathError.path保存上下文信息,而wrapError和msg成员则把原error和上下文保存到一起,通过err成员个保存原始的error
此处的wrapError与之前的errorString相比,还额外实现了Unwrap()接口,用于返回原始的error
2.fmt,Errorf()
fmt.Errorf()新增了格式动词%w(wrap)用于生成wrapError实例,并且兼容原有格式动词。fmt.Errorf()的实现
fmt.Errorf()将根据格式动词来动态决定生成wrrapError还是errorString.使用%v格式动词生成的error类型仍是errorString(没有实现Unwrap接口),如一下Example测试
func ExampleCreateBasicError() { err := errors.New("this is demo error") basicErr := fmt.Errorf("some context: %v", err) // 使用 %v if _, ok := basicErr.(interface{ Unwrap() error }); !ok { // 如果errBasic没有实现Unwrap接口 fmt.Print("basicErr is a errorString") } // Output: // basicErr is a errorString }而使用%w格式动词生成的error类型将自动变成wrapError(实现了Unwrap接口),如一下Example所示:
func ExampleCreateWrapError() { err := errors.New("this is demo error") wrapError := fmt.Errorf("some context: %w", err) // 使用%w if _, ok := wrapError.(interface{ Unwrap() error }); ok { // 如果wrapError实现了Unwrap接口 fmt.Print("wrapError is a wrapError") } // Output: // wrapError is a wrapError }这样,当error在函数间传递时,error之间好像被组织成一个链式结构,使用fmt.Errorf()生成wrapError有两个限制
- 每次生成wrapError时只能使用一次%w动词
- %w动词只能匹配实现了error接口的参数
(1)
由wrapError的数据结构可知,每个wrapError只能包含一个原始error信息,所以fmt.Errorf()函数每次只能接受一个%w动词
wrapError := fmt.Errorf("sone context %w, %w",err,err)编译器将给出“Errorf call has more than one error-wrapping directive %w”的错误提示
(2)%w只匹配error参数
由于格式动词%w专用于生成链式的error,所以fmt.Errorf()函数只能匹配实现了error接口的参数
wrapError := fmt.Errorf("sone context: %w","some string")编译器将给出“Errorf format %w has arg "some string” of wrong type string"的错误提示
另外需要注意的是,虽然wrapError实现了Unwrap()接口,但由于error接口仍然只定义了一个Error()方法,所以使fmt.Errorf()生成的error,不能直接调用自身的Unwrap()接口获得原始error,而需要使用errors包中提供的Unwrap()方法
3.errors.Unwrap()
Unwrap()函数很形象的揭示了其作用,如果把error比作一件衣服,fmt.Errorf()(使用%w)就好比给error增加了意见外套,而Unwrap()函数则是脱掉外套
如果参数err没有实现Unwrap()函数,则说明是基础error,直接返回nil,否则调用原err实现的Unwrap()函数并返回
对于自定义的error类型,在实现Error()函数的基础上,需要额外实现Unwrao函数,可以升级成链式error,如os.PathError:
type PathError struct { Op string Path string Err string } func (e *PathError) Error()string{ return e.Op + " " + e.Path + ": " + e.Err.Error() } func (e *PathError) Unwrap() error { return e.Err }使用Unwrap获取原始error并进行比较的例子如下:
func ExampleUnwrap() { err := fmt.Errorf("write file error: %w", os.ErrPermission) if errors.Unwrap(err) == os.ErrPermission { fmt.Printf("permission denied") } // Output: // permission denied }在上面的例子中原始的error(os.ErrPermission)只被包裹了一层,我们可以使用Unwrap()函数获取原始的error,在实际的应用中,原始的error可能在层层的函数调用中被包裹了多层,如何进行判断?对此,我们可以循环调用errors.Unwrap()方法来逐层检查
如下:
func ExampleUnwrapLoop() { err1 := fmt.Errorf("write file error: %w", os.ErrPermission) err2 := fmt.Errorf("write file error: %w", err1) err := err2 for { if err == os.ErrPermission { fmt.Printf("permission denied") break } if err = errors.Unwrap(err); err == nil { break } } // Output: // permission denied }可以不使用上面的循环,errors包的ls()方法对此提供了更好的支持
4.errors.Is()
errors.Is用于检查特定的error链中是否包含指定的error值,error.Is的伪代码如下:
func Is(err, target error)bool { for { if err == target{ return true } //自定义的error类型如果实现了Is方法,则会咨询Is()方法 if x,ok := err.(interface{ Is(error) bool });ok && x.Is(target){ return true } if err = Unwrap(err);err == nil{ return false } } }errors.Is()逐层拆解参数err并于参数target对比,如果发现相等则返回true,否则返回false.对于自定义error类型来说,如果实现了Is()方法,则此处在比较时会先咨询自身的Is()方法
使用errors.Is()方法优化上面的Example,代码将变得格外简洁:
func ExampleIs() { err1 := fmt.Errorf("write file error: %w", os.ErrPermission) err2 := fmt.Errorf("write file error: %w", err1) if errors.Is(err2, os.ErrPermission) { fmt.Printf("permission denied") } // Output: // permission denied }
5.errors.As()
检查error时往往还会做类型转换,即使用断言来检测error是否是指定类型,比如:
if e,ok := err.(*os.PathError);ok{ fmt.Printf("it's an os.PathError, operation; %s, path: %s, msg: %v\n",e.Op, e.Path, e.Err) }类似的,如果err是一个链式error,那么上面的代码将不再有效,比如下面的Example将没有任何打印结果:
unc ExampleAssertChanErrorWithoutAs() { err := &os.PathError{ Op: "write", Path: "/root/demo.txt", Err: os.ErrPermission, } err2 := fmt.Errorf("some context: %w", err) if e, ok := err2.(*os.PathError); ok { // 判断失效 fmt.Printf("it's an os.PathError, operation: %s, path: %s, msg: %v\n", e.Op, e.Path, e.Err) } // Output: // }errors.As()用于从一个error链中查找是否有指定的类型出现,如有,则把error转换成该类型。errors.As()的实现代码如下:
func As(err error,target interface{}) bool { val := reflectlite.TYpeOf(err).AssignableTo(target) typ := val.Type() targetType := typ.Elem() for err !=nil { if reflectlite.TypeOf(err).AssignableTo(targetType) //如果类型匹配,则将err赋值 val.Elem().Set(reflectlite.ValueOf(err)) return true } if x,ok := err.(interface{As(interface{}) bool });ok && x.As(target) { //如果err实现了As()方法,那么也会咨询As() return true } err = Unwrap(err) } return false }与Is()的实现非常类似,As()会逐层拆解error链并尝试匹配指定类型,如果类型匹配,则将err值写入参数target.同时,如果error实现了As()函数,那么也尝试咨询As()函数
使用errors.AAs()优化上面的代码:
func ExampleAssertChanWithAs() { err := &os.PathError{ Op: "write", Path: "/root/demo.txt", Err: os.ErrPermission, } err2 := fmt.Errorf("some context: %w", err) var target *os.PathError if errors.As(err2, &target) { // 逐层剥离err2并检测是否是os.PathError类型 fmt.Printf("it's an os.PathError, operation: %s, path: %s, msg: %v\n", target.Op, target.Path, target.Err) } // Output: // it's an os.PathError, operation: write, path: /root/demo.txt, msg: permission denied }
6.小结
通过扩展fmt.Errorf()来支持创建链式的error,并通过errors.Unwrap()来拆解error
errors.Is()递归地拆解error并检查是否指定的error值,类型于基础error中的哨兵检查,errors.As()递归地拆解error并检查是否是指定的error类型,如是则将error写入指定的变量中