[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

推薦閱讀