那么interface的真实长相是什么呢,我们看看go1.15.2的实现:
// src/runtime/runtime2.go // 因为这边没使用空接口,所以只节选了含数据接口的实现 type iface struct { tab *itab data unsafe.Pointer } // src/runtime/runtime2.go 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. } // src/runtime/type.go type imethod struct { name nameOff ityp typeOff } type interfacetype struct { typ _type pkgpath name mhdr []imethod // 类型所包含的全部方法 } // src/runtime/type.go type _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all pointers hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool // gcdata stores the GC type data for the garbage collector. // If the KindGCProg bit is set in kind, gcdata is a GC program. // Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte str nameOff ptrToThis typeOff }没有给出定义的类型都是对各种整数类型的typing alias。interface实际上就是存储类型信息和实际数据的struct,自动解引用后编译器是直接查看内存内容的(见汇编),这时看到的其实是iface这个普通类型,所以静态查找一个不存在的方法就失败了。而为什么手动解引用的代码可以运行?因为我们手动解引用后编译器可以推导出实际类型是interface,这时候编译器就很自然地用处理interface的方法去处理它而不是直接把内存里的东西寻址后塞进寄存器。
总结其实也没什么好总结的。只有两点需要记住,一是interface是有自己对应的实体数据结构的,二是尽量不要用指针去指向interface,因为golang对指针自动解引用的处理会带来陷阱。
如果你对interface的实现很感兴趣的话,这里有个reflect+暴力穷举实现的乞丐版。
理解了乞丐版的基础上如果有兴趣还可以看看真正的golang实现,数据的层次结构上更细化,而且有使用指针和内存偏移等的聪明办法,不说是否会有收获,起码研究起来不会无聊:P。