首先要明確go中的interface分為兩種,無方法聲明和有方法聲明的的,對應源碼中的定義如下:
//有方法聲明type iface struct { tab *itab data unsafe.Pointer}//無方法聲明type eface struct { _type *_type data unsafe.Pointer}
其中data指向實際的值資訊,_type是對定義內部類型資訊的資料結構,itab裡定義了介面相關資訊,包括介面類型、實際類型、實現的方法集等:
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是關於一個介面本身的資訊,包括這個介面的類型、包含的方法集以及包名等。
iface則對應的是把一個struct轉為interface後的資訊。
fun中存放了interfacetype需要的方法集的具體實現,因為方法記憶體中是順序存放的,所以fun中只需要儲存第一個方法地址的指標。
hashSize = 1009hash [hashSize]*itab
可能是為了效能考慮,所有的itab實際是存放在一個全域hash表中,當把一種interface類型轉為另一種interface類型時,調用的源碼是:
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}
可以看到當i中tab的inter和要轉的inter相同時,直接就可以轉換,否則要調用getitab擷取待轉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
上面的代碼,首先是用inter和typ計算一個hash值,然後看全域itab的hash表中是否含有這個hash值的itab,如果有並且這個itab的inter和typ符合條件,則證明之前已經有過類似轉換,符合類型轉換條件。如果沒有,再試圖調用additab:
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))}
省略符號的部分是判斷m的typ是否實現了inter方法集的邏輯,如果沒有全部實現,證明類型轉換不合法m.bad會被置為true。如果轉換合法,最後會把m存入全域itab hash表中。
參考文章:
- http://legendtkl.com/2017/07/01/golang-interface-implement/
- https://www.tapirgames.com/blog/golang-interface-implementation