今天寫代碼的時候,發(fā)現(xiàn)一個和預(yù)期不符合的邏輯。簡單描述就是把一個值為nil的A類型的error賦值給接口error,和nil比較竟然是false。在此記錄一下。
復(fù)現(xiàn)
代碼如下,自定義一個error類型。然后預(yù)計代碼輸出是<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)
}
解決辦法
不要將該結(jié)果賦給一個接口變量。如,將 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 造成的。具體可以查看 官網(wǎng) FAQ。
簡單說,interface 被兩個元素 value 和 type 所表示。只有在 value 和 type 同時為 nil 的時候,判斷 interface == nil 才會為 true。而 err = returnErr() 這個過程中,雖然 value 為 nil,但 type 卻為 *Err。
下面的內(nèi)容從《Go語言精進之路:從新手到高手編程思想、方法和技巧1》拷貝
接口類型“動靜兼?zhèn)洹钡奶匦詻Q定了它的變量的內(nèi)部表示絕不像靜態(tài)類型(如int、float64)變量那樣簡單。我們可以在$GOROOT/src/runtime/runtime2.go中找到接口類型變量在運行時的表示:
image.png
我們看到在運行時層面,接口類型變量有兩種內(nèi)部表示——eface和iface,這兩種表示分別用于不同接口類型的變量。eface:用于表示沒有方法的空接口(empty interface)類型變量,即interface{}類型的變量。iface:用于表示其余擁有方法的接口(interface)類型變量。這兩種結(jié)構(gòu)的共同點是都有兩個指針字段,并且第二個指針字段的功用相同,都指向當前賦值給該接口類型變量的動態(tài)類型變量的值。
image.png
而iface除了要存儲動態(tài)類型信息之外,還要存儲接口本身的信息(接口的類型信息、方法列表信息等)以及動態(tài)類型所實現(xiàn)的方法的信息,因此iface的第一個字段指向一個itab類型結(jié)構(gòu):
image.png
上面itab結(jié)構(gòu)中的第一個字段inter指向的interfacetype結(jié)構(gòu)存儲著該接口類型自身的信息。interfacetype類型定義如下,該interfacetype結(jié)構(gòu)由類型信息(typ)、包路徑名(pkgpath)和接口方法集合切片(mhdr)組成。
itab結(jié)構(gòu)中的字段_type則存儲著該接口類型變量的動態(tài)類型的信息,字段fun則是動態(tài)類型已實現(xiàn)的接口方法的調(diào)用地址數(shù)組。


