我已经实现了一个自定义错误类型,并且在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")
}
}
在操场上相同:https://play.golang.org/p/7bqeDw5B5fU
这确实输出“错误不是零”。
我原本期望将* CustomError类型的nil值隐式转换为类型error的nil值。任何人都可以向我解释,为什么不是这种情况以及如何正确传播自定义错误类型的nil值?
修改 正如Iain Duncan所指出的here可以找到对此的解释
为了进一步探讨这个问题,让我们考虑WrapFailCustom的以下修改:
func WrapFailCustom(dofail bool) error {
err := FailCustom(dofail)
if err == nil {
return nil
} else {
return err
}
}
这确实会返回“err is nil”:https://play.golang.org/p/mEKJFyk5zqf
我确实感觉非常糟糕,依赖于此作为解决方案,因为在使用吐出我的自定义错误的函数时很容易忘记。是否有更好的方法来制作自定义错误,以防止这种“歧义”发生?一直使用基本错误类型的明显解决方案对于使用WrapFailCustom等功能的代码来说似乎非常不方便,所以我想避免这种...
答案 0 :(得分:4)
有关背景信息,请参阅Hiding nil values, understanding why golang fails here;和Go FAQ: Why is my nil error value not equal to nil?
Go要求您明确说明类型和转换(例如,您不能将类型int32
的值添加到类型int
的值),但接口转换和自动接口值创建是这个规则的例外。每当需要接口类型的值时,您可以使用其类型实现(满足)接口类型的任何值,并且将自动为您创建接口值。
你的职能:
func WrapFailCustom(dofail bool) error {
return FailCustom(dofail)
}
WrapFailCustom()
的返回类型为error
,您尝试返回FailCustom()
函数的结果,其返回类型为*CustomError
,与error
不同{1}}!这应该引起一面红旗!
将返回什么/如何?将自动创建error
类型的接口值! *CustomError
实现了error
,所以一切都很好,但正如您所经历的那样,如果指针是nil
,则此隐式值换行不会导致接口值为nil
,而是包含值nil
和类型nil
的非*CustomError
接口值。
解决方案?
它是否真的合理/ FailCustom()
是否有error
以外的返回类型?如果不是,那么最简单的方法就是处理它所源自的“问题”:
func FailCustom(dofail bool) error {
if dofail {
return &CustomError{Code: 42}
}
return nil
}
然后你的所有问题都消失了。如果您按照“Go way”使用error
类型返回错误,这就足够了,令人满意。您甚至不再需要WrapFailCustom()
功能。
WrapFailCustom()
如果您确实需要FailCustom()
返回自定义*CustomError
类型,则需要在WrapFailCustom()
中“手动”对其进行说明。我会这样写:
func WrapFailCustom(dofail bool) error {
if customErr := FailCustom(dofail); customErr != nil {
return customErr
}
return nil
}
(请注意,我故意使用了不同的customErr
名称,而不是err
,表明它不属于error
类型,应注意如何将其转换为{{ 1}}。)
error
类型如果您想/需要使用自定义错误类型,另一个好方法是创建一个描述其包含的“额外”功能的接口类型:
error
然后我们还需要实现这个type CustomErr interface {
Error // Embed error interface
Code() int
}
方法:
Code()
这有什么用?
通过返回此接口类型的值(以及不指针)来处理根本原因:
func (e *CustomError) Code() int { return e.Code }
隐式接口值将在func FailCustom(dofail bool) CustomErr {
if dofail {
return &CustomError{Code: 42}
}
return nil
}
中创建。
此外,FailCustom()
变得不必要/无用。 WrapFailCustom()
返回的值均为FailCustom()
,您可以使用其error
方法从中获取Code
。返回的值是接口值, 是Code()
,您可以在需要error
值的地方使用它。具体类型error
甚至可以被取消导出(隐藏)。
与此方法相关,请查看Dave Cheney: Don’t just check errors, handle them gracefully,尤其是标题为“断言行为错误,而不是”的部分。