nil error 值 != nil

nil error 值 != nil

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)相比自然不相等。

解决的主要思路是避免自动装箱:

  1. 把returnsError()里面p的类型改为error。
  2. 将returnError()的返回值类型改为*MyError。
  3. 删除p,直接return &ErrBad或者nil。

Viewpoint #

From #

29|接口:为什么nil接口不等于nil?