在 Go 语言中,error 接口是非常基础的,它定义了一个 Error() 方法,该方法返回一个描述错误的字符串。这种设计允许开发者创建自定义的错误类型,只需实现 Error() 方法即可。
type error interface {
Error() string
}
内置包errors中实现也很简单
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
调用New方法就可以生成一个error对象
e1:=errors.New("错误1")
error对象是可以比较的,但是即使New方法入参字符串相同,两个error对象也不相等,因为是两个不同的结构体对象实例
var e1 = errors.New("错误1")
var e2 = errors.New("错误2")
fmt.Println(e1 == e2)// false
自定义error
知道了一个结构体只要实现Error()string方法就是error了,接下来就可以自定义error结构体了,可以在满足接口后再自定义一些方法
type MyErr struct {
msg string
}
func NewError(msg string) error {
return &MyErr{msg: msg}
}
func (m MyErr) Error() string {
return m.msg
}
func (m MyErr) Is(error) bool {
return true
}
errors.Is
errors.Is 函数是Go 1.13版本引入的一个非常有用的错误处理工具,它用于判断一个错误值是否等于另一个错误值,或者是否由某个特定的错误值通过调用errors.Wrap或errors.WithMessage(这两个函数是github.com/pkg/errors包提供的,但在Go 1.13及以后的标准库中,通过%w动词在fmt.Errorf中也支持了类似的错误包装功能)等方式包装而来。
简单来说,errors.Is用于检查一个错误链中是否包含某个特定的错误。这对于在多层调用的函数中传递和识别错误非常有用,因为它允许调用者在不修改中间层代码的情况下,对错误进行精确的判断和处理。
err1 := errors.New("original error")
err2 := fmt.Errorf("something bad happened: %w", err1)
if errors.Is(err2, err1) {
fmt.Println("ok")
} else {
fmt.Println("no")
}
在上述例子中,会打印ok,因为在判断过程中,如果结构体有Is或Unwrap方法会被调用,fmt.Errorf是拥有Unwrap方法的。
接下来看看errors.Is的源码解析
// Is 这个方法的含义是判断入参错误是否为目标错误
func Is(err, target error) bool {
// 如果目标错误为nil,则直接判断入参错误是否为nil
if target == nil {
return err == target
}
/*
判断目标是否为可比较类型
map slice chan等类型是不可比较的
int float bool string pointer 等是可比较的
struct 要看元素是否都是可比较的类型
*/
isComparable := reflect.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
// is 递归判断两个错误是否相等
func is(err, target error, targetComparable bool) bool {
for {
// 可比较并且相等
if targetComparable && err == target {
return true
}
// err有自定义的Is方法,调用Is方法自己判断
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
switch x := err.(type) {
// err有自定义的Unwrap方法,调用Unwrap方法,并将返回值作为新的err继续判断
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if is(err, target, targetComparable) {
return true
}
}
return false
default:
return false
}
}
}
errors.As
errors.As 函数允许你检查一个错误值是否可以被视为某个特定类型的错误。如果可以将错误值断言为指定的类型,则该函数将错误值转换为该类型并存储在提供的目标变量中,同时返回true。如果断言失败,则目标变量不会被修改,函数返回false。
e1 := errors.New("e1 error")
e2 := errors.New("e2 error")
fmt.Println(errors.As(e1, &e2)) // true
fmt.Println(e2.Error()) // e1 error
源码解析
func As(err error, target any) bool {
if err == nil {
return false
}
val := reflect.ValueOf(target)
targetType := val.Type().Elem()
return as(err, target, val, targetType)
}
// as 循环、递归判断
func as(err error, target any, targetVal reflect.Value, targetType reflect.Type) bool {
for {
// 判断是否可以赋值,如果可以,则将err赋值给target,并返回true
if reflect.TypeOf(err).AssignableTo(targetType) {
targetVal.Elem().Set(reflect.ValueOf(err))
return true
}
// err有自定义的As方法,调用As方法自己判断
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
switch x := err.(type) {
// err有自定义的Unwrap方法,调用Unwrap方法,并将返回值作为新的err继续判断(处理嵌套error)
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if err == nil {
continue
}
if as(err, target, targetVal, targetType) {
return true
}
}
return false
default:
return false
}
}
}
语法解析
- err实现了errors接口,它还有一些自己的方法,
err.(interface{ As(any) bool })这个语法是将err类型断言为
interface{
As(any) bool
}
如果断言成功,说明err结构体上有As(any) bool这个方法