如果函数或方法返回错误,要比对的不单只是nil
与否,例如,读取文件时,会需要判断返回的错误是否为io.EOF
,那么io.EOF
这些错误是什么呢?在io
包的io.go源码中可以看到,它们就是个errors.New
建出的值罢了:
var ErrShortWrite = errors.New("short write")
var ErrShortBuffer = errors.New("short buffer")
var EOF = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
在errors
包的errors.go可以看到,errors.New
创建的是个结构值,只有一个string
字段,并且实现了Error
方法:
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
字符串是可以比较的(Comparable),errorString
结构也是个可以比较的,因此可以直接使用==
来比较错误是否为io.EOF
等,在开发自己的应用程序或程序库时,对于通用、简单的错误,也可以如上定义。
errors.New
创建的实例,能携带的信息就只是字符串罢了,如果错误发生时,需要传递更多的环境信息,怎么办呢?
在方法定义返回错误时的error
其实是个内置的接口,定义的正是Error
方法:
type error interface {
Error() string
}
也就是说,只要有实现Error
方法,都可以作为error
实例返回,例如,os.PathError
在os
包的error.go是这么定义的:
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
func (e *PathError) Unwrap() error { return e.Err }
func (e *PathError) Timeout() bool {
t, ok := e.Err.(timeout)
return ok && t.Timeout()
}
也就是说,若错误是PathError
实例,可以有透过字段或者是方法来获取更多信息,例如:
if e, ok := err.(*PathError); ok {
// 透过 e 获取字段或调用方法
}
若要多种类型要判断,可以使用类型switch
语法,例如os
包的error.go内部实现就有个例子:
func underlyingError(err error) error {
switch err := err.(type) {
case *PathError:
return err.Err
case *LinkError:
return err.Err
case *SyscallError:
return err.Err
}
return err
}
像PathError
中还包含了Err
字段,这并非必要,其应用的情境是在调用某函数时检查到错误,除了创建另一个错误实例收集当时的环境信息之外,你可能会想要包裹来源的错误实例,以便后续调用者可以进一步检视错误根源。
然而,当某个错误包裹了另一个错误,也就表示后续调用者得知道该错误的细节,如果这些细节来自另一个底层,而你不想曝露,就不要直接包裹它,这时在目前应用程序或程序库的抽象层面中,抽取出来源错误中的信息,包装为目前层次的错误就可以了。