1. 程式人生 > 程式設計 >Go “一個包含nil指標的介面不是nil介面”踩坑

Go “一個包含nil指標的介面不是nil介面”踩坑

最近在專案中踩了一個深坑——“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,%T\n",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專案

參考文章:

Golang第一大坑

"一個包含nil指標的介面不是nil介面"的討論