First of all, the interface in Go is divided into two types, no method declaration and method declaration, the corresponding source code is defined as follows:
//有方法声明type iface struct { tab *itab data unsafe.Pointer}//无方法声明type eface struct { _type *_type data unsafe.Pointer}
Where data points to the actual value information, _type is the data structure that defines the internal type information, and ITAB defines the interface-related information, including the interface type, the actual type, the method set of the implementation, and so on:
type itab struct { inter *interfacetype _type *_type link *itab hash uint32 // copy of _type.hash. Used for type switches. bad bool // type does not implement interface inhash bool // has this itab been added to hash? unused [2]byte fun [1]uintptr // variable sized}
InterfaceType is information about an interface itself, including the type of the interface, the set of methods contained, and the package name.
Iface corresponds to the message that a struct is converted to interface.
Fun contains the specific implementation of the set of methods required by InterfaceType, because the methods are stored sequentially in memory, so only a pointer to the first method address needs to be stored in fun.
hashSize = 1009hash [hashSize]*itab
Perhaps for performance reasons, all itab are actually stored in a global hash table, and when one interface type is converted to another interface type, the source code is called:
func convI2I(inter *interfacetype, i iface) (r iface) { tab := i.tab if tab == nil { return } if tab.inter == inter { r.tab = tab r.data = i.data return } r.tab = getitab(inter, tab._type, false) r.data = i.data return}
You can see that when I tab in the Inter and the Inter to go to the same time, can be converted directly, otherwise, call Getitab to get R itab.
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {... h := itabhash(inter, typ) var m *itab var locked int for locked = 0; locked < 2; locked++ { if locked != 0 { lock(&ifaceLock) } for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link { if m.inter == inter && m._type == typ {... return m } } m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys)) m.inter = inter m._type = typ additab(m, true, canfail) unlock(&ifaceLock) if m.bad { return nil } return m
The above code, the first is to use inter and Typ to calculate a hash value, and then see whether the Global Itab hash table contains the hash value of the itab, if there is and this Itab Inter and Typ meet the conditions, it proves that there has been a similar conversion, matching the type conversion conditions. If not, try calling Additab again:
func additab(m *itab, locked, canfail bool) { inter := m.inter typ := m._type ... h := itabhash(inter, typ) m.link = hash[h] m.inhash = true atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))}
The ellipsis part is to determine whether the Typ of M implements the logic of the Inter method set, and if it is not fully implemented, proves that the type conversion is not legal M.bad will be put to true. If the conversion is legal, then the M will be deposited into the global itab hash table.
Reference article:
- http://legendtkl.com/2017/07/01/golang-interface-implement/
- Https://www.tapirgames.com/blog/golang-interface-implementation