[Go疑难杂症]为什么nil不等于nil( 二 )

TV 同时为 nil 时,这个变量才会被判定为 nil,所以该不等式会判定为 true
要修复这个问题,其实最简单的方法便是在调用 doUpdate 方法时给 err 进行重新声明:
if err := txn.doUpdate(); err != nil {log.Fatalf("err updating: %v", err)}此时,err 其实成了一个新的结构体指针变量,而不再是一个interface 类型变量 , 类型为 *CustomizedError ,且值为 nil,所以做 err ≠ nil 的比较时结果就是将是 false
问题到这里似乎就告一段落了,但,再仔细想想 , 就会发现这其中似乎还是漏掉了一环 。
如果给一个 interface 类型的变量赋值时,会同时改变它的类型 T 和值 V,那跟 nil 比较时为什么不是跟它的新类型对应的 nil 比较呢?
事实上,interface 变量跟普通变量确实有一定区别,一个非空接口 interface (即接口中存在函数方法)初始化的底层数据结构是 iface,一个空接口变量对应的底层结构体为 eface
type iface struct { tab*itab data unsafe.Pointer}type eface struct { _type *_type dataunsafe.Pointer}tab 中存放的是类型、方法等信息 。data 指针指向的 iface 绑定对象的原始数据的副本 。
再来看一下 itab 的结构:
// layout of Itab known to compilers// allocated in non-garbage-collected memory// Needs to be in sync with// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.type itab struct { inter *interfacetype _type *_type hashuint32 // copy of _type.hash. Used for type switches. _[4]byte // 用于内存对齐 fun[1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.}itab 中一共包含 5 个字段,inner 字段存的是初始化 interface 时的静态类型 。_type 存的是 interface 对应具体对象的类型,当 interface 变量被赋值后,这个字段便会变成被赋值的对象的类型 。
itab 中的 _typeiface 中的 data 便分别对应 interface 变量的 TV_type 是这个变量对应的类型,data 是这个变量的值 。在之前的赋值测试中,通过 reflect.TypeOfreflect.ValueOf 方法获取到的信息也分别来自这两个字段 。
这里的 hash 字段和 _type 中存的 hash 字段是完全一致的,这么做的目的是为了类型断言 。
fun 是一个函数指针,它指向的是具体类型的函数方法,在这个指针对应内存地址的后面依次存储了多个方法,利用指针偏移便可以找到它们 。
再来看看 interfacetype 的结构:
type interfacetype struct { typ_type pkgpath name mhdr[]imethod}这其中也有一个 _type 字段 , 来表示 interface 变量的初始类型 。
看到这里,之前的疑问便开始清晰起来,一个 interface 变量实际上有两个类型 , 一个是初始化时赋值时对应的 interface 类型,一个是赋值具体对象时 , 对象的实际类型 。
了解了这些之后,我们再来看一下之前的例子:
txn, err := startTx()这里先对 err 进行初始化赋值,此时,它的 itab.inter.typ 对应的类型信息就是 erroritab._type 仍为 nil
【[Go疑难杂症]为什么nil不等于nil】err = txn.doUpdate()当对 err 进行重新赋值时,erritab._type 字段会被赋值成 *CustomizedError  , 所以此时,err 变量实际上是一个 itab.inter.typerror ,但实际类型为 *CustomizedError ,值为 nil 的接口变量 。
把一个具体类型变量与 nil 比较时,只需要判断其 value 是否为 nil 即可,而把一个接口类型的变量与 nil 进行比较时,还需要判断其类型 itab._type 是否为nil
如果想实际看看被赋值后 err 对应的 iface 结构 , 可以把 iface 相关的结构体都复制到同一个包下,然后通过 unsafe.Pointer

推荐阅读