- Interface types are one special kind of type in Go.
- it just declare the methods.
- duck typing: User-defined types that satisfying the interface will implicitily implement the interface.
- static, checked at compile time, dynamic when asked for.
- all type constraints are actually interface types.
- When a value is boxed in an interface value, the value is called the dynamic value of the interface value.
- If a type
Timplements a (basic) interface typeI, then any value of typeTcan be implicitly converted to typeI. In other words, any value of typeTis assignable to (modifiable) values of typeI. - When a
Tvalue is converted (assigned) to anIvalue,- if type
Tis a non-interface type, then a copy of theTvalue is boxed (encapsulated) into the resultIvalue. The time complexity of the copy isO(n), wherenis the size of copiedTvalue. - if type
Tis also an interface type, then a copy of the value boxed in theTvalue is boxed (or encapsulated) into the result (or destination)Ivalue. The standard Go compiler makes an optimization here, so the time complexity of the copy isO(1), instead ofO(n).
- if type
iface:interfacewith at least 1 methodeface: emptyinterface{}data: the acutal dataitab: the type conversion between interface and typeinter: points to interface type
_type: the type ofdata(generate at compilation time)kind: e.g. bool, int, float, string, struct, interface
type iface struct { // 16 bytes on a 64bit arch
tab *itab
data unsafe.Pointer
}-
An interface is thus a very simple structure that maintains 2 pointers:
tabholds the address of anitabobject, which embeds the datastructures that describe both the type of the interface as well as the type of the data it points to.datais a raw (i.e.unsafe) pointer to the value held by the interface.
-
Since interfaces can only hold pointers, any concrete value that we wrap into an interface will have to have its address taken.
i1 = A
i1 data
┌────────────┐ ┌─────────────┐
│ _type │ │ copy of A │
├────────────┤ │ │◄─ ─┐
│ 0x123456 ├──────────────────►│ 0x123456 │
└────────────┘ └─────────────┘ │
│ copy from
i2 = &A
i2 data │
┌────────────┐ ┌─────────────┐
│ _type │ │ Value A │ │
├────────────┤ │ ├── ─┘
│ 0xabcdef ├──────────────────►│ 0xabcdef │
└────────────┘ └─────────────┘
i1 = A, value A is copied to new address0x123456ofdatai2 = &A, data points to address of value A0xabcdef.
type itab struct { // 40 bytes on a 64bit arch
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}An itab is the heart & brain of an interface.
First, it embeds a _type, which is the internal representation of any Go type within the runtime.
A _type describes every facets of a type: its name, its characteristics (e.g. size, alignment...), and to some extent, even how it behaves (e.g. comparison, hashing...)!
In this instance, the _type field describes the type of the value held by the interface, i.e. the value that the data pointer points to.
Second, we find a pointer to an interfacetype, which is merely a wrapper around _type with some extra information that are specific to interfaces.
As you'd expect, the inter field describes the type of the interface itself.
Finally, the fun array holds the function pointers that make up the virtual/dispatch table of the interface.
Notice the comment that says // variable sized, meaning that the size with which this array is declared is irrelevant.
We'll see later in this chapter that the compiler is responsible for allocating the memory that backs this array, and does so independently of the size indicated here. Likewise, the runtime always accesses this array using raw pointers, thus bounds-checking does not apply here.
// TODO
// TODO
