常见问题解答说明:
对于返回错误的函数,最好始终在其签名中使用错误类型(就像我们上面所做的那样),而不是使用 *MyError 等具体类型,以帮助保证正确创建错误。例如, os.Open 返回一个错误,即使它不是 nil,它始终是具体类型 *os.PathError。
但是这篇文章声称有时可以使用具体类型返回:
(由于 Go FAQ 中讨论的原因,传回错误的具体类型而不是错误通常是错误的,但在这里这样做是正确的,因为
是唯一看到该值并使用其值的地方内容。)ServeHTTP
我的问题是,使用
error
返回而不是具体类型有什么好处,它如何帮助保证正确创建错误,以及为什么当错误未使用时可以返回具体类型不同地方?
这与
error
作为接口有关。接口包含一个指向它所包含的值的指针以及该值的类型。仅当这两个值都为零时,接口才为零。因此,如果您从函数返回具体的错误类型,然后返回 error
,则该错误将不会为零。
type MyError string
func (e MyError) Error() string {return string(e)}
func f() *MyError {
return nil
}
func g() error {
return f()
}
func main() {
x:=g()
if x==nil {
fmt.Println("nil")
}
}
在上面的示例中,即使
*MyError
的值为 nil,但 g()
的返回值却不是,因为它包含一个类型为 *MyError
且值为 nil 的接口。
对于所有返回接口值的函数来说,这是一个问题,并且最常见于
error
类型。因此,不要声明返回具体错误值的函数,除非这些函数的用途非常有限,例如未导出的函数。
Francesc Campoy 在 GophenCon 2016 - Understanding nil 讲座中解释了原因。
特别是在 when is nil not nil 和 不要声明具体错误变量部分。
他提出的观点是,尽管您可以使用具体的错误类型作为返回类型,但它会按预期工作。但如果稍后您或其他开发人员将其转换为
error
接口,它会意外中断。然后突然间 err == nil
检查会给出“错误结果”,因为接口仅被视为 nil
除非 它的类型和值都是 nil。
所以你可以从像这样的具体错误类型开始(坏主意):
func do() *doError { // concrete error type as return value
return nil // nil of type *doError
}
func main() {
err := do() //nil of type *doError
fmt.Println(err == nil) // true
}
如果稍后您或其他人引入一个新函数
wrapDo
,它返回一个error
接口而不是具体类型*doType
:
func do() *doError { //
return nil // nil of type *doError
}
func wrapDo() error { // this return error interface not concrete type
return do() // nil of type *doError gets converted to (*doError, nil)
}
func main() {
err := wrapDo() // error interface type (*doError, nil)
fmt.Println(err == nil) // false!!!, surprise, surprise
}
正如您在上面所看到的,
nil
类型的*doError
被转换为error
接口,类型为*doError
和值nil
,并且不是nil*,类型和值都需要为零就被认为是零。