预计将于 2023 年 2 月发布的 Go 1.20 有一个小的变化,对于那些大量使用错误包装的应用程序来说,可能会有效改进它们的错误处理方法。
让我们看一下它的用法,但首先,需要简要回顾一下什么是错误包装。如果你已经掌握了可以直接跳到下面的 “Go 1.20 新功能” 部分以获取新的信息。
Go 中的错误是实现一个非常简单的接口:
type error interface {
Error() string
}
错误类型可以是任何东西,从 string
本身到 int
,但通常它们是 struct
类型。下面这个例子来自标准库:
type err struct {
s string
}
func (e *err) Error() string {
return e.s
}
要检查 Go 中的错误,你只需比较一个值(在本例中为 int
值):
if err == io.EOF {
// ...
}
第二种常见的用法是检查错误的类型,那也意味着要写更多的代码:
if nerr, ok := err.(net.Error) {
// ... (use nerr which is a net.Error)
}
在上面的例子中,类型断言测试类型 net.Error
的 err
值,并创建一个新变量 nerr
,它可以在 if 语句中使用。Go 中的错误方便理解、易于使用且非常高效。
错误包装
从 Go 1.13 开始,引入了错误包装。包装允许将错误嵌入到其他错误中,就像在其他语言中包装异常一样。这非常实用,比如函数遇到 “record not found” 错误时,可以向错误信息中添加更多上下文信息,例如 “unknown user: record not found”。
Go 中错误包装设计背后的有趣想法是:契约不用关心错误类型、结构或它们是如何创建的。而唯一关注的是解包过程和转换为字符串,因为这两者是必须的。这就非常容易实现:支持解包的错误类型必须实现 Unwrap() error
方法。
标准库中没有(命名的)接口可以向您展示,因为接口是隐式实现的,没有必要单独写一个。这里我们写一个只是为了更好说明这篇文章:
type WrappedError interface {
Unwrap() error
}
我们来看看 Go 标准库(实际上是 package fmt)中是如何实现包装错误的:
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
由于上面错误类型实现了 Error() string
方法,所以说 Go 中的错误实际上最终是 字符串
并没有错,因此需