漫行记Wandering
Journal 3,166 字 11 分钟 Golang

Golang Interface: A Deep Dive

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 an itab struct, 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, then funcs[0] is the function pointer for the first method, and funcs[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 _type struct.
  • 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 funcs array.

Example

var r io.Reader = os.Stdin
  1. Check interface satisfaction
    • Runtime sees r is of type io.Reader, which is an interface with one method: Read(p []byte) (n int, err error).
    • Checks if *os.File (the type of os.Stdin) implements io.Reader by verifying it has a Read method with the correct signature.
  2. Locate or build itab
    • if no (interface type, concrete type) pair exists in the global itab cache, it constructs a new itab.
    • for each method in io.Reader, it finds the corresponding method in *os.File and stores its function pointer in the funcs array of the itab.
  3. Store interface value
    • Creates an iface struct for r.
    • Sets the tab field to point to the constructed itab.
    • Sets the data field to point to the actual os.Stdin value.
← 随笔