There are two kinds of interfaces in Go: empty interface and non-empty interface. They have different underlying implementations. Let’s go through them one by one.
Empty Interface
An empty interface is defined as interface{} and can hold values of any type. And it has no methods.
The underlying implementation of an empty interface is a struct called eface, which is defined in the
runtime2.go
type eface struct {
_type *_type
data unsafe.Pointer
}
_type: This is a pointer to the type information of the value stored in the interface.data: This is a pointer to the actual data of the value stored in the interface.
Because there is no method in an empty interface, it only needs to store the type information and the data pointer, it is a very simple structure compared to a non-empty interface.
Non-Empty Interface
A non-empty interface is defined as interface{ Method1(); Method2() } and can hold values of any type that
implement the methods defined in the interface. Compare to an empty interface, apparently, we need to store more
information.
Actually, we use iface struct to represent a non-empty interface, it is also defined in
the runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}
itab: This is a pointer to anitabstruct, which contains information about the interface type and the concrete type that implements the interface. and it also contains a list of function pointers for the methods defined in the interface.data: This is a pointer to the actual data of the value stored in the interface.
and the itab struct is defined
in internal/abi/iface.go
type ITab struct {
Inter *InterfaceType // interface type
Type *_type // concrete type
Hash uint32 // copy of Type.hash. Used for type switches.
funcs [1]uintptr // variable sized. fun[0]==0 means _type does not implement Inter.
}
type InterfaceType struct {
Type
PkgPath Name // import path
Methods []Imethod // sorted by hash
}
Inter: This is a pointer to the interface type information.Type: This is a pointer to the concrete type information that implements the interface.Hash: This is a hash value of the concrete type, used for type switches.funcs: This is an array of function pointers for the methods defined in the interface. The size of this array is variable, depending on the number of methods in the interface. As we can see, we use index to access the method. for example, there are two methods in the interface, thenfuncs[0]is the function pointer for the first method, andfuncs[1]is the function pointer for the second method. So it highly depends on the order of the methods defined.
How to build itab
we need to know what information is needed to build an itab.
- Inter and Type: golang build the type information when compiling the code, so we can get the type information directly.
- Hash: we can get the hash value from the
_typestruct. - funcs: we need to go through the methods defined in the interface and find the
corresponding method
in the concrete type, then we can get the function pointer and store it in the
funcsarray.
Example
var r io.Reader = os.Stdin
- Check interface satisfaction
- Runtime sees
ris of typeio.Reader, which is an interface with one method:Read(p []byte) (n int, err error). - Checks if
*os.File(the type ofos.Stdin) implementsio.Readerby verifying it has aReadmethod with the correct signature.
- Runtime sees
- Locate or build
itab- if no
(interface type, concrete type)pair exists in the globalitabcache, it constructs a newitab. - for each method in
io.Reader, it finds the corresponding method in*os.Fileand stores its function pointer in thefuncsarray of theitab.
- if no
- Store interface value
- Creates an
ifacestruct forr. - Sets the
tabfield to point to the constructeditab. - Sets the
datafield to point to the actualos.Stdinvalue.
- Creates an