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