共计 3462 个字符,预计需要花费 9 分钟才能阅读完成。
原文链接: Go Error 嵌套到底是怎么实现的?
Go Error 的设计哲学是「Errors Are Values」。
这句话应该怎么了解呢?翻译起来挺难的。不过从源码的角度来看,如同更容易了解其背地的含意。
Go Error 源码很简略,寥寥几行:
// src/builtin/builtin.go
type error interface {Error() string
}
error
是一个接口类型,只须要实现 Error()
办法即可。在 Error()
办法中,就能够返回自定义构造体的任意内容。
上面首先说说如何创立 error
。
创立 Error
创立 error
有两种形式,别离是:
errors.New()
;fmt.Errorf()
。
errors.New()
errors.New()
的应用连续了 Go 的一贯格调,New
一下就能够了。
举一个例子:
package main
import (
"errors"
"fmt"
)
func main() {err := errors.New("这是 errors.New() 创立的谬误")
fmt.Printf("err 谬误类型:%T,谬误为:%v\n", err, err)
}
/* 输入
err 谬误类型:*errors.errorString,谬误为:这是 errors.New() 创立的谬误
*/
这段代码惟一让人困惑的中央可能就是谬误类型了,但没关系。只有看一下源码,就霎时迎刃而解。
源码如下:
// src/errors/errors.go
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
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}
能够看到,errorString
是一个构造体,实现了 Error()
办法,New
函数间接返回 errorString
指针。
这种用法很简略,但不实用。如果我还想返回程序的上下文信息,它就没辙了。
上面看第二种形式。
fmt.Errorf()
还是先看一个例子:
package main
import (
"database/sql"
"fmt"
)
func foo() error {return sql.ErrNoRows}
func bar() error {return foo()
}
func main() {err := bar()
if err == sql.ErrNoRows {fmt.Printf("data not found, %+v\n", err)
return
}
if err != nil {fmt.Println("Unknown error")
}
}
/* 输入
data not found, sql: no rows in result set
*/
这个例子输入了咱们想要的后果,然而还不够。
个别状况下,咱们会通过应用 fmt.Errorf()
函数,附加上咱们想增加的文本信息,使返回内容更明确,解决起来更灵便。
所以,foo()
函数会改成上面这样:
func foo() error {return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}
这时问题就呈现了,通过 fmt.Errorf()
的封装,原始 error
类型产生了扭转,这就导致 err == sql.ErrNoRows
不再成立,返回信息变成了 Unknown error
。
如果想依据返回的 error
类型做不同解决,就无奈实现了。
因而,Go 1.13 为咱们提供了 wrapError
来解决这个问题。
Wrap Error
看一个例子:
package main
import ("fmt")
type myError struct{}
func (e myError) Error() string {return "Error happended"}
func main() {e1 := myError{}
e2 := fmt.Errorf("E2: %w", e1)
e3 := fmt.Errorf("E3: %w", e2)
fmt.Println(e2)
fmt.Println(e3)
}
/* output
E2: Error happended
E3: E2: Error happended
*/
乍一看如同好没什么区别,但背地的实现原理却并不相同。
Go 扩大了 fmt.Errorf()
函数,减少了一个 %w
标识符来创立 wrapError
。
// src/fmt/errors.go
func Errorf(format string, a ...interface{}) error {p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {err = errors.New(s)
} else {err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
当应用 w%
时,函数会返回 &wrapError{s, p.wrappedErr}
,wrapError
构造体定义如下:
// src/fmt/errors.go
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {return e.msg}
func (e *wrapError) Unwrap() error {return e.err}
实现了 Error()
办法,阐明它是一个 error
,而 Unwrap()
办法是为了获取被封装的 error
。
// src/errors/wrap.go
func Unwrap(err error) error {
u, ok := err.(interface {Unwrap() error
})
if !ok {return nil}
return u.Unwrap()}
它们之间的关系是这样的:
因而,咱们能够应用 w%
将上文中的程序进行革新,使其内容输入更丰盛。
如下:
package main
import (
"database/sql"
"errors"
"fmt"
)
func bar() error {if err := foo(); err != nil {return fmt.Errorf("bar failed: %w", foo())
}
return nil
}
func foo() error {return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
}
func main() {err := bar()
if errors.Is(err, sql.ErrNoRows) {fmt.Printf("data not found, %+v\n", err)
return
}
if err != nil {fmt.Println("Unknown error")
}
}
/* output
data not found, bar failed: foo failed: sql: no rows in result set
*/
终于有了让人称心的输入后果,每个函数都减少了必要的上下文信息,而且也合乎对谬误类型的判断。
errors.Is()
函数用来判断 err
以及其封装的 error
链中是否蕴含指标类型。这也就解决了上文提出的无奈判断谬误类型的问题。
后记
其实,Go 目前对 Error 的解决形式也是充斥争议的。不过,官网团队正在踊跃和社区交换,提出改良办法。置信在不久的未来,肯定会找到更好的解决方案。
现阶段来说,大部分团队可能会抉择 github.com/pkg/errors
包来进行错误处理。如果感兴趣的话,能够学学看。
好了,本文就到这里吧。 关注我,带你通过问题读 Go 源码。
源码地址:
- https://github.com/yongxinz/gopher
举荐浏览:
- 为什么要防止在 Go 中应用 ioutil.ReadAll?
- [如何在 Go 中将 []byte 转换为 io.Reader?](https://mp.weixin.qq.com/s/nF…)
- 开始读 Go 源码了
参考文章:
- https://chasecs.github.io/pos…
- https://medium.com/@dche423/g…
- https://www.flysnow.org/2019/…