Pet*_*mel 2 error-handling null pointers interface go
我已经实现了一个自定义错误类型,并且在 nil 值方面确实得到了奇怪的行为。当我将自定义错误作为标准错误接口传递时,这永远不会被识别为 nil,即使自定义错误返回为 nil。
看看这个小测试程序:
package main
import (
"fmt"
"strconv"
)
type CustomError struct {
Code int
}
func (e *CustomError) Error() string {
return strconv.Itoa(e.Code)
}
func FailCustom(dofail bool) *CustomError {
if dofail {
return &CustomError{Code: 42}
} else {
return nil
}
}
func WrapFailCustom(dofail bool) error {
return FailCustom(dofail)
}
func main() {
err := WrapFailCustom(false)
if err == nil {
fmt.Println("err is nil")
} else {
fmt.Println("err is not nil")
}
}
Run Code Online (Sandbox Code Playgroud)
在操场上也一样:https : //play.golang.org/p/7bqeDw5B5fU
这实际上输出 "err is not nil"。
我本来希望 *CustomError 类型的 nil 值被隐式转换为 error 类型的 nil 值。任何人都可以向我解释,为什么不是这种情况以及如何正确传播自定义错误类型的 nil 值?
编辑:正如 Iain Duncan 所指出的, 可以在此处找到对此的解释
为了进一步探讨这个问题,让我们考虑对 WrapFailCustom 进行以下修改:
func WrapFailCustom(dofail bool) error {
err := FailCustom(dofail)
if err == nil {
return nil
} else {
return err
}
}
Run Code Online (Sandbox Code Playgroud)
这实际上返回“错误为零”:https : //play.golang.org/p/mEKJFyk5zqf
依赖这个作为解决方案,我确实感觉很糟糕,因为在使用会吐出我的自定义错误的函数时很容易忘记它。是否有更好的方法来制作自定义错误,以防止这种“歧义”发生?一直使用基本错误类型的明显解决方案,对于使用像 WrapFailCustom 这样的函数的代码来说似乎非常不方便,所以我想避免这种情况......
有关背景信息,请参阅隐藏 nil 值,了解 golang 为什么会在这里失败;和Go FAQ:为什么我的 nil 错误值不等于 nil?
Go 要求您明确类型和转换(例如,您不能将 type 值添加到 typeint32值int),但接口转换和自动接口值创建是此规则的例外。每当需要接口类型的值时,您可以使用任何类型实现(满足)接口类型的值,接口值将自动为您创建。
你的功能:
func WrapFailCustom(dofail bool) error {
return FailCustom(dofail)
}
Run Code Online (Sandbox Code Playgroud)
WrapFailCustom()有一个返回类型error,而你试图返回FailCustom()函数的结果,其返回类型*CustomError与error! 这应该引起一个危险信号!
什么/如何返回?error将自动创建类型为接口值!*CustomError实现error,所以一切都很好,但是正如您所经历的,如果指针是nil,则这种隐式值包装不会导致接口值是nil,而是nil包装值nil和类型的非接口值*CustomError。
解决方案?
它真的有道理/它有任何价值,FailCustom()除了返回类型error吗?如果没有,最简单的方法是处理“问题”的根源:
func FailCustom(dofail bool) error {
if dofail {
return &CustomError{Code: 42}
}
return nil
}
Run Code Online (Sandbox Code Playgroud)
然后你所有的问题都会消失。如果您按照“Go 方式”使用该error类型返回错误,这已经足够且令人满意。您甚至WrapFailCustom()不再需要该功能。
WrapFailCustom()如果确实需要FailCustom()返回自定义*CustomError类型,则需要在WrapFailCustom(). 我会这样写:
func WrapFailCustom(dofail bool) error {
if customErr := FailCustom(dofail); customErr != nil {
return customErr
}
return nil
}
Run Code Online (Sandbox Code Playgroud)
(请注意,我故意使用了不同的customErr名称而不是err,表明它不是类型error,并且应注意如何将其转换为error。)
error作为接口类型的自定义类型如果您想/需要使用自定义错误类型,另一种好方法是创建一个接口类型来描述它包含的“额外”功能:
type CustomErr interface {
Error // Embed error interface
Code() int
}
Run Code Online (Sandbox Code Playgroud)
然后我们还需要实现这个Code()方法:
func (e *CustomError) Code() int { return e.Code }
Run Code Online (Sandbox Code Playgroud)
这有什么用?
将通过返回此接口类型的值(而不是指针)来处理根本原因:
func FailCustom(dofail bool) CustomErr {
if dofail {
return &CustomError{Code: 42}
}
return nil
}
Run Code Online (Sandbox Code Playgroud)
隐式接口值将在FailCustom().
此外,WrapFailCustom()变得不必要/无用。FailCustom()返回一个既是 an 的值,error您可以Code使用它的Code()方法从中获取。返回值是一个接口值,它是一个error,你可以在需要error值的地方使用它。具体类型CustomError甚至可以不导出(隐藏)。
与此方法相关,请查看Dave Cheney:不要只检查错误,要优雅地处理它们,尤其是标题为:Assert errors for behavior, not type 的部分。