天天看點

Go“一個包含nil指針的接口不是nil接口”踩坑

Go“一個包含nil指針的接口不是nil接口”踩坑

最近在項目中踩了一個深坑——“Golang中一個包含nil指針的接口不是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, %T\n", i, i)
}
           

這段代碼的執行結果是:

importantFunc傳回了一個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接口的結論:

hello()           
fmt.Println("github.com/meetbetter")           

//錯誤:将nil的people給空接口後接口就不為nil,因為interface中的value為nil但type不為nil

func errFunc() *People {

var p *People

return p           

//正确處理傳回nil給接口的方式:直接将nil賦給interface

func rightFunc() IPeople {

var p *People

return p           
if errFunc() == nil {

    fmt.Println("對了哦,外部接收到也是nil")
} else {

    fmt.Println("錯了咦,外部接收到不是nil哦")

}

if rightFunc() == nil {

    fmt.Println("對了哦,外部接收到也是nil")
} else {

    fmt.Println("錯了咦,外部接收到不是nil哦")

}
           

輸出結果:

對了哦,外部接收到也是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項目。

參考文章:

Golang第一大坑

"一個包含nil指針的接口不是nil接口"的讨論

原文位址

https://www.cnblogs.com/CodeWithTxT/p/11297300.html

繼續閱讀