深入理解Go的interface内部执行原理

Go的interface是由两种类型来实现的: iface 和 eface
iface指的是接口中申明有方法(至少1个) , eface表示接口中没有申明方法
后面会讲到这两个到底是什么 , 所以这里需要先不用关心 。
深入理解下面是一个简单的Demo , Binary实现了fmt.Stringer接口 , 我们调用 ToString() 方法 , 会调用接口的 String() 方法 。
// 类型type Binary uint64// 实现String方法 , 实现fmt.Stringer接口func (i Binary) String() string { return strconv.FormatUint(uint64(i), 10)}func main() { b := Binary(200) // 01// var b Binary = Binary(200) ToString(b)// 02}func ToString(value interface{}) string {// 断言 , 转化成fmt.Stringer接口类型 newValue, ok := value.(fmt.Stringer) //3 if ok {return newValue.String()// 4 } panic("The valueis not implement fmt.Stringer func")}大致的执行流程图下所示:
//01 执行的是 , 在内存中开辟一块内存 , 存放200这个值

深入理解Go的interface内部执行原理

文章插图
 
// 02 调用ToString方法 , 首先方法传递过程中需要隐式将b转换成interface{}类型 , 实际上做的就是以下:
深入理解Go的interface内部执行原理

文章插图
 
首先大家可能会关心 , 我就没见过这个结构 , 你是不是骗人的 , 其实有这个结构体 , 是在 runtime/runtime2.go 中
type eface struct { _type *_type // 类型 dataunsafe.Pointer //值}那么如何转换的呢?
type 指得是 Binary的类型 , 包含了Binary类型的所有信息(后面会介绍到)
data 指向的真实数据 , 由于我们传递的不是指针 , 所以这种情况下其实是做了一次内存拷贝( 所以也就是尽可能的别使用interface{} ) , data其实存的是拷贝的数据 , 如果换做是指针 , 其实也是拷贝了一份指针地址(这也就是reflect.Elem方法的作用)
? 以下这几段代码全部来自于 runtime/iface.go
// 关于 unsafe.Pointer , unsafe包学习的时候介绍过func convT2E(t *_type, elem unsafe.Pointer) (e eface) { if raceenabled {raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E)) } if msanenabled {msanread(elem, t.size) }// 首先会分配一块内存 , 内存大小为类型t的大小,下面这段话是mallocgc的介绍// Allocate an object of size bytes.// Small objects are allocated from the per-P cache's free lists.// Large objects (> 32 kB) are allocated straight from the heap. x := mallocgc(t.size, t, true) // TODO: We allocate a zeroed object only to overwrite it with actual data. // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.// 将elem拷贝到xtypedmemmove(t, x, elem)// eface 的类型为t , 值为x e._type = t e.data = https://www.isolves.com/it/cxkf/cxy/2020-06-21/x return}//3 其次就是到了断言的部分 , 那么断言到底执行了什么呢?
// inter 指的是fmt.Stringer接口类型信息// e 就是我们上面的的interface{} 的真实类型efacefunc assertE2I2(inter *interfacetype, e eface) (r iface, b bool) { t := e._type if t == nil {return }// 获取tab , 其实大家有可能不太理解 tab := getitab(inter, t, true) if tab == nil {return } r.tab = tab r.data = https://www.isolves.com/it/cxkf/cxy/2020-06-21/e.data b = true return}那么这里就需要理解什么是 iface
type iface struct { tab*itab//table data unsafe.Pointer //值}【深入理解Go的interface内部执行原理】tab 又是什么?
? tab的意思是table的意思 , 关于table的概念 , 大家可以去找找资料? 具有方法的语言通常属于以下两种阵营之一:为所有方法调用静态地准备表(如在C ++和JAVA中) , 或在每次调用时进行方法查找(如在Smalltalk及其许多模仿程序中 , 包括JavaScript和Python)以及添加奇特的缓存以提高调用效率 。Go位于两者的中间:它具有方法表 , 但在运行时对其进行计算 。type itab struct { inter *interfacetype// 接口类型,这里就是Stringer _type *_type// 值类型, 这里就是Binary hashuint32 // copy of _type.hash. Used for type switches. _[4]byte fun[1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.}其实上面这段代码的流程如下:
data就是 eface.data
tab 其实就是 : inter 指的是接口类型(也就是fmt.Stringer接口) ,  type 是Binary类型 ,  fun[0] 是(Binary)String方法  , 其他几个先不用care


推荐阅读