Многие знают, что интерфейс в Go реализован как обертка, внутри которой два указателя — на конкретное значение и на его тип.
Но строго говоря, все несколько сложнее.
Вот интерфейс:
type iface struct {
tab *abi.ITab
data unsafe.Pointer
}data — это действительно указатель. Если мы заворачиваем в интерфейс значение, в data будет указатель на него (а само значение переедет в кучу при необходимости).
Если же заворачиваем указатель (например
*os.File в io.Reader), то в data будет ровно этот самый указатель.А вот tab — не просто конкретный тип, а целая пачка метаданных:
type ITab struct {
Inter *InterfaceType
Type *Type
Hash uint32
Fun [1]uintptr
}Inter — это тип интерфейса (например,
io.Reader), Type — конкретный тип (например, *os.File).Hash — это хеш от конкретного типа. Он используется для быстрого выбора подходящей ветки в переключателях типа (type switch). О нем еще поговорим отдельно.
А Fun — это массив указателей на методы типа, которые реализуют интерфейс (например,
(*os.File).Read).Из объявления
Fun [1]uintptr может показаться, что у интерфейса может быть только один метод (что, как мы знаем, не так). Но на самом деле это фикция. Указателей будет ровно столько, сколько методов у интерфейса, просто Fun[0] — первый из них.Ах да, пустой интерфейс (any) реализован иначе. О нем тоже отдельно поговорим.
Уфф.