golang中的interface = nil问题

50 阅读1分钟

如下代码

package note

import (
	"fmt"
	"testing"
)

type Animal interface {
	Speak()
}

type Dog struct {
}

func (d *Dog) Speak() {
	fmt.Print("dog speak")
}

func TestInterface(T *testing.T) {
	var animal Animal
	if animal == nil {
		fmt.Println("nil animal")
	}

	var dog *Dog
	if dog == nil {
		fmt.Println("nil dog")
	}
	animal = dog
	if animal == nil {
		fmt.Println("nil animal")
	}

}

最终输出

=== RUN   TestInterface
nil animal
nil dog
--- PASS: TestInterface (0.00s)
PASS

可以看到当dog(nil)赋值给animal(nil)之后,animal不再为nil

原因如下

具体在runtime/runtime2.go


type iface struct {
    tab  *itab
    data unsafe.Pointer
}

// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WritePluginTable.
type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
  • tab:指向 itab 结构的指针,该结构包含该类型的信息以及该类型为接口实现的方法

  • data: 指向接口实际数据的指针

  • inter *interfacetype:指向接口类型的指针。interfacetype 是表示接口类型的结构体。

  • _type *_type:指向具体类型的指针。_type 是表示具体类型的结构体。

  • hash uint32:具体类型的哈希值副本,用于类型转换。该哈希值是 _type.hash 的副本,用于在类型转换中快速比较类型。

  • _ [4]byte:填充字节,用于内存对齐。这是为了确保结构体的内存布局符合对齐要求。

  • fun [1]uintptr:变长数组,存储函数指针。数组的第一个元素 fun[0] 用于指示具体类型是否实现了接口。如果 fun[0] 等于 0,表示具体类型没有实现接口。

当执行赋值操作时,创建的iface结构体的tab不为nil,inter为&dog,_type为Dog这个类型,而data则是一个为nil的结构体&dog,所以赋值后的animal不为nil