T
和 V
同时为 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
中的 _type
和 iface
中的 data
便分别对应 interface
变量的 T
和 V
,_type
是这个变量对应的类型,data
是这个变量的值 。在之前的赋值测试中,通过 reflect.TypeOf
与 reflect.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
对应的类型信息就是 error
itab._type
仍为 nil
。
【[Go疑难杂症]为什么nil不等于nil】err = txn.doUpdate()
当对 err
进行重新赋值时,err
的 itab._type
字段会被赋值成 *CustomizedError
, 所以此时,err
变量实际上是一个 itab.inter.typ
为 error
,但实际类型为 *CustomizedError
,值为 nil
的接口变量 。
把一个具体类型变量与 nil
比较时,只需要判断其 value
是否为 nil
即可,而把一个接口类型的变量与 nil
进行比较时,还需要判断其类型 itab._type
是否为nil
。
如果想实际看看被赋值后 err
对应的 iface
结构 , 可以把 iface
相关的结构体都复制到同一个包下,然后通过 unsafe.Pointer
推荐阅读
- 黑鲨4pro为什么不建议买_黑鲨4pro骂声一片原因
- 电脑?号怎么打出来(电脑为什么打不出来字)
- 羊了个羊为什么进不去
- 羊了个羊通关率为什么会不到0.1%
- McAfee如何卸载干净(mcafee为什么卸载不了)
- 语音发出后录音还在继续_语音发出之后为什么还在录音
- 不吹不黑,锤子手机真的好用吗为什么(现在的锤子手机怎么样)
- 红心大战到底怎么玩儿?为什么我总是多分(红心大战得分越低越好吗)
- golang中的nil接收器
- 为什么说:保险柜没有钥匙时,只用笔帽就可以打开怎么做到的