Go「一個包含nil指針的介面不是nil介面」踩坑
- 2019 年 10 月 3 日
- 筆記
最近在項目中踩了一個深坑——「Golang中一個包含nil指針的介面不是nil介面」,現象是函數內返回了nil給一個對象,使用interface接收函數返回值判斷始終不為nil。總結下分享出來,如果你不是很理解這句話,那推薦認真看下下面的示例程式碼,避免以後寫程式碼時踩坑。
示例一
先一起來看下這段程式碼,你感覺有沒有問題呢?
type IPeople interface { hello() } type People struct { } func (p *People) hello() { fmt.Println("github.com/meetbetter") } func errFunc1(in int) *People { if in == 0 { fmt.Println("importantFunc返回了一個nil") return nil } else { fmt.Println("importantFunc返回了一個非nil值") return &People{} } } func main() { var i IPeople in := 0 i = errFunc1(in) if i == nil { fmt.Println("哈,外部接收到也是nil") } else { fmt.Println("咦,外部接收到不是nil哦") fmt.Printf("%v, %Tn", i, i) } }
這段程式碼的執行結果是:
importantFunc返回了一個nil 咦,外部接收到不是nil哦 <nil>, *main.People
可以看到在main函數中收到的返回值不是nil, 明明在errFunc1()函數中返回的是nil,到了main函數為什麼收到的不是nil呢?
這是因為:將nil賦值給*People
後再將*People
賦值給interface,*People
本身是是個指向nil的指針,但是將其賦給介面時只是介面中的值為nil,但是介面中的類型資訊為*main.People
而不是nil,所以這個介面不是nil。
是的,Golang中的interface類型包含兩部分資訊——值資訊和類型資訊,只有interface的值合併類型都為nil時interface才為nil,interface底層實現可以在後面的源碼分析看到。
先來看看正確的處理介面返回值的方法,是直接將nil賦給interface:
func rightFunc(in int) IPeople { if in == 0 { fmt.Println("importantFunc返回了一個nil") return nil } else { fmt.Println("importantFunc返回了一個非nil值") return &People{} } }
示例二
下面的程式碼更清晰的證明了一個包含nil指針的介面不是nil介面
的結論:
type IPeople interface { hello() } type People struct { } func (p *People) hello() { fmt.Println("github.com/meetbetter") } //錯誤:將nil的people給空介面後介面就不為nil,因為interface中的value為nil但type不為nil func errFunc() *People { return nil } //正確處理返回nil給介面的方法,返回時go就確定了介面是不是nil func rightFunc() IPeople { return nil } func main() { var i IPeople i = errFunc() if i == nil { //想通過介面是否為nil來判斷故障,卻始終判斷介面非空 fmt.Println("errFunc對了哦,外部接收到也是nil") fmt.Println(reflect.TypeOf(i)) } else { fmt.Println("errFunc錯了咦,外部接收到不是nil哦") fmt.Println(reflect.TypeOf(i)) } i = rightFunc() if i == nil { fmt.Println("rightFunc對了哦,外部接收到也是nil") fmt.Println(reflect.TypeOf(i)) } else { fmt.Println("rightFunc錯了咦,外部接收到不是nil哦") fmt.Println(reflect.TypeOf(i)) } }
輸出結果:
errFunc錯了咦,外部接收到不是nil哦 *main.People rightFunc對了哦,外部接收到也是nil <nil>
interface底層實現
下面的注釋資訊來自參考文章中,從interface底層實現可以看出iface比eface 中間多了一層itab結構, itab 存儲_type資訊和[]fun方法集,所以即使data指向了nil 並不代表interface 就是nil, 還要考慮_type資訊。
type eface struct { //空介面 _type *_type //類型資訊 data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*) } type iface struct { //帶有方法的介面 tab *itab //存儲type資訊還有結構實現方法的集合 data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*) } type _type struct { size uintptr //類型大小 ptrdata uintptr //前綴持有所有指針的記憶體大小 hash uint32 //數據hash值 tflag tflag align uint8 //對齊 fieldalign uint8 //嵌入結構體時的對齊 kind uint8 //kind 有些枚舉值kind等於0是無效的 alg *typeAlg //函數指針數組,類型實現的所有方法 gcdata *byte str nameOff ptrToThis typeOff } type itab struct { inter *interfacetype //介面類型 _type *_type //結構類型 link *itab bad int32 inhash int32 fun [1]uintptr //可變大小 方法集合 }
以上完整程式碼均整理在Github-跟著示例程式碼學Golang項目。
參考文章: