为什么建议在go中返回`error`接口而不是具体的错误类型?

问题描述 投票:0回答:2

常见问题解答说明

对于返回错误的函数,最好始终在其签名中使用错误类型(就像我们上面所做的那样),而不是使用 *MyError 等具体类型,以帮助保证正确创建错误。例如, os.Open 返回一个错误,即使它不是 nil,它始终是具体类型 *os.PathError

但是这篇文章声称有时可以使用具体类型返回:

(由于 Go FAQ 中讨论的原因,传回错误的具体类型而不是错误通常是错误的,但在这里这样做是正确的,因为

ServeHTTP
是唯一看到该值并使用其值的地方内容。)

我的问题是,使用

error
返回而不是具体类型有什么好处,它如何帮助保证正确创建错误,以及为什么当错误未使用时可以返回具体类型不同地方?

go struct error-handling interface
2个回答
4
投票

这与

error
作为接口有关。接口包含一个指向它所包含的值的指针以及该值的类型。仅当这两个值都为零时,接口才为零。因此,如果您从函数返回具体的错误类型,然后返回
error
,则该错误将不会为零。

在 Go 游乐场上直播

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
类型。因此,不要声明返回具体错误值的函数,除非这些函数的用途非常有限,例如未导出的函数。


0
投票

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*,类型和值都需要为零就被认为是零。

© www.soinside.com 2019 - 2024. All rights reserved.