[Go疑難雜癥]為什么nil不等于nil

現象在日常開發中,可能一不小心就會掉進 Go 語言的某些陷阱里,而本文要介紹的 nil ≠ nil 問題,便是其中一個,初看起來會讓人覺得很詭異,摸不著頭腦 。
先來看個例子:
type CustomizedError struct { ErrorCode int Msgstring}func (e *CustomizedError) Error() string { return fmt.Sprintf("err code: %d, msg: %s", e.ErrorCode, e.Msg)}func main() { txn, err := startTx() if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}type tx struct{}func startTx() (*tx, error) { return &tx{}, nil}func (*tx) doUpdate() *CustomizedError { return nil}func (*tx) commit() error { return nil}這是一個簡化過了的例子,在上述代碼中,我們創建了一個事務,然后做了一些更新,在更新過程中如果發生了錯誤,希望返回對應的錯誤碼和提示信息 。
如果感興趣的話,可以在這個地址在線運行這份代碼:
Go Playground - The Go Programming Language
看起來每個方法都會返回 nil,應該能順利走到最后一行,輸出 success 才對 , 但實際上,輸出的卻是:
err updating: <nil>尋找原因為什么明明返回的是 nil,卻被判定為 err ≠ nil 呢?難道這個 nil 也有什么奇妙之處?
這就需要我們來更深入一點了解 error 本身了 。在 Go 語言中,error 是一個 interface  , 內部含有一個 Error() 函數 , 返回一個字符串,接口的描述如下:
// The error built-in interface type is the conventional interface for// representing an error condition, with the nil value representing no error.type error interface { Error() string}而對于一個變量來說 , 它有兩個要素 , 一個是 type T,一個是 value V , 如下圖所示:

[Go疑難雜癥]為什么nil不等于nil

文章插圖
來看一個簡單的例子:
var it interface{}fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // <nil> <invalid reflect.Value>it = 1fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // int 1it = "hello"fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // string hellovar s *stringit = sfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string <nil>ss := "hello"it = &ssfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string 0xc000096560在給一個 interface 變量賦值前,TV 都是 nil , 但給它賦值后 , 不僅會改變它的值,還會改變它的類型 。
當把一個值為 nil 的字符串指針賦值給它后 , 雖然它的值是 V=nil , 但它的類型 T 卻變成了 *string 。
此時如果拿它來跟 nil 比較,結果就會是不相等,因為只有當這個 interface 變量的類型和值都未被設置時,它才真正等于 nil 。
再來看看之前的例子中,err 變量的 TV 是如何變化的:
func main() { txn, err := startTx() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}輸出如下:
<nil> <invalid reflect.Value>*err.CustomizedError <nil>在一開始,我們給 err 初始化賦值時,startTx 函數返回的是一個 error 接口類型的 nil 。此時查看其類型 T 和值 V 時,都會是 nil 。
txn, err := startTx()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // <nil> <invalid reflect.Value>func startTx() (*tx, error) { return &tx{}, nil}而在調用 doUpdate 時,會將一個 *CustomizedError 類型的 nil 值賦值給了它,它的類型 T 便成了 *CustomizedError ,V 是 nil 。
err = txn.doUpdate()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // *err.CustomizedError <nil>所以在做 err ≠ nil 的比較時,err 的類型 T 已經不是 nil,前面已經說過,只有當一個接口變量的

推薦閱讀