nil error 值 != nil #
我们直接来看一段改编自 Go FAQ中的例子的代码:
type MyError struct {
error
}
var ErrBad = MyError{
error: errors.New("bad things happened"),
}
func bad() bool {
return false
}
func returnsError() error {
var p *MyError = nil
if bad() {
p = &ErrBad
}
return p
}
func main() {
err := returnsError()
if err != nil {
fmt.Printf("error occur: %+v\n", err)
return
}
fmt.Println("ok")
}
returnsError 这个函数里定义了一个*MyError类型的变量 p,初值为 nil。如果函数 bad 返回 false,returnsError 函数就会直接将 p(此时 p = nil)作为返回值返回给调用者,之后调用者会将 returnsError 函数的返回值(error 接口类型)与 nil 进行比较,并根据比较结果做出最终处理。
如果你是一个初学者,我猜你的的思路大概是这样的:p 为 nil,returnsError 返回 p,那么 main 函数中的 err 就等于 nil,于是程序输出 ok 后退出。但真实的运行结果是什么样的呢?我们来看一下:
error occur: <nil>
示例程序并未如我们前面预期的那样输出 ok。程序显然是进入了错误处理分支,输出了 err 的值。那这里就有一个问题了:明明 returnsError 函数返回的 p 值为 nil,为什么却满足了if err != nil的条件进入错误处理分支呢?
问题的关键在于returnsError的返回值类型被声明为error接口。从 returnsError 返回的 error 接口类型变量 err 的数据指针虽然为空,但它的类型信息(iface.tab)并不为空,而是 *MyError 对应的类型信息,这样 err 与 nil(0x0,0x0)相比自然不相等。
解决的主要思路是避免自动装箱:
- 把returnsError()里面p的类型改为error。
- 将returnError()的返回值类型改为*MyError。
- 删除p,直接return &ErrBad或者nil。