Go 的 interface 設計很怪?建議新手看看
前幾天同事突然拋過來一張截圖,很驚詫地說,Go 的 Interface 設計很怪。我一看,原來是一個示例程序,大體的內容如下:
示例代碼
package main
import (
"fmt"
)
type KK interface {
HappyBirthday()
}
func main() {
var my interface{}
if my == nil {
fmt.Println("mikk")
} else {
fmt.Println("not nil")
}
var kk KK
if kk == nil {
fmt.Println("mikk")
} else {
fmt.Println("not nil")
}
}
示例代碼運行結果
mikk
mikk
從運行結果來看,my 和 kk 都是空值,同事的疑惑是:明明 kk 的原型 KK 有 HappyBirthday 的方法聲明,直覺上不應該是 nil 啊。
Go 源碼相關內容
看到同事的疑惑以後,我的第一反應是想到自己翻看過的 Go 源碼。
源碼中的 nil
在 go/src/builtin/builtin.go 的源碼中,對 nil 的定義如下:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
// 零值,特製 指針、信道、函數、接口、map、slice
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
大體意思是:nil 代表零值;特別地,代表指針、信道、函數、接口 (Interface)、map、slice 的零值。也就是說,Interface 的零值本就是 nil。
而根據 Go 的特點,var name Type 的聲明方式中 name 的默認值本就是零值,因此無論是 my 還是 kk,都等於 nil 也便可以理解了。
源碼中的 Interface
Interface 主要其作用的地方是在運行時,因此這裏主要介紹 go/src/runtime/runtime2.go 中的兩個片段。
// 帶有方法的接口
type iface struct {
// 存儲_type信息還有結構實現方法的集合
tab *itab
//指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*)
data unsafe.Pointer
}
// 空接口
type eface struct {
//類型信息
_type *_type
// 指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*)
data unsafe.Pointer
}
從源碼可以知道,無論是 iface 還是 eface,其底層均存在一個 data 字段,也就是說,無論是帶方法的接口還是空接口,其的存在必然要依附有意義的數值(零值也是有意義的)纔可以。可以簡單認爲一塊內存,裏面存放着變量,哪怕這個變量存放的全是零值也無所謂,但是必須要有這麼一個內存變量,也就是說,只有這塊內存存在了,Interface 的存在纔有意義。
再看一個例子
package main
import (
"fmt"
)
type Angel interface {
Show()
}
type Girl struct{}
func (girl *Girl) Show() {
}
func miss() Angel {
var g *Girl
return g
}
func main() {
angel := miss()
if angel == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
}
上面的代碼輸出 not nil。
具體的,雖然 miss() 返回了零值的 g,但是 data 這時候已經有值了(雖然在這裏還是個零值)。這種情況下,angel 就不是 nil 了,因爲它已經有了 data。
本文主要對比了 Go 的 Interface 的兩個例子,並從源碼角度簡單剖析了 Interface 的本質。
原文:https://jingwei.link/2018/06/30/golang-interface-analysis.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fMMR_EiVfTIbFjA3WwebEQ