今天写代码的时候,发现一个和预期不符合的逻辑。简单描述就是把一个值为nil的A类型的error赋值给接口error,和nil比较竟然是false。在此记录一下。
复现
代码如下,自定义一个error类型。然后预计代码输出是<nil> false,但是实际输出是<nil> true。非常非常奇怪,nil竟然不等于nil
package main
import "fmt"
type Err struct {
err string
}
func (e *Err) Error() string {
return e.err
}
func returnErr() *Err {
return nil
}
func main() {
var err error
err = returnErr()
fmt.Println(err, err != nil)
}
解决办法
不要将该结果赋给一个接口变量。如,将 err = returnErr() 改成 err1 := returnErr()
func main() {
err := returnErr()
fmt.Println(err, err != nil)
}
或者和原类型比较
func main() {
var err error
err = returnErr()
var e *Err
fmt.Println(err, err != e)
}
原因
接口 interface 造成的。具体可以查看 官网 FAQ。
简单说,interface 被两个元素 value 和 type 所表示。只有在 value 和 type 同时为 nil 的时候,判断 interface == nil
才会为 true。而 err = returnErr()
这个过程中,虽然 value 为 nil,但 type 却为 *Err。
下面的内容从《Go语言精进之路:从新手到高手编程思想、方法和技巧1》拷贝
接口类型“动静兼备”的特性决定了它的变量的内部表示绝不像静态类型(如int、float64)变量那样简单。我们可以在$GOROOT/src/runtime/runtime2.go中找到接口类型变量在运行时的表示:
我们看到在运行时层面,接口类型变量有两种内部表示——eface和iface,这两种表示分别用于不同接口类型的变量。eface:用于表示没有方法的空接口(empty interface)类型变量,即interface{}类型的变量
。iface:用于表示其余拥有方法的接口(interface)类型变量
。这两种结构的共同点是都有两个指针字段,并且第二个指针字段的功用相同,都指向当前赋值给该接口类型变量的动态类型变量的值。
而iface除了要存储动态类型信息之外,还要存储接口本身的信息(接口的类型信息、方法列表信息等)以及动态类型所实现的方法的信息,因此iface的第一个字段指向一个itab类型结构:
上面itab结构中的第一个字段inter指向的interfacetype结构存储着该接口类型自身的信息。interfacetype类型定义如下,该interfacetype结构由类型信息(typ)、包路径名(pkgpath)和接口方法集合切片(mhdr)组成。
itab结构中的字段_type则存储着该接口类型变量的动态类型的信息,字段fun则是动态类型已实现的接口方法的调用地址数组。