Interfaces are the key to data types in Go programming. In the actual programming of Go language, almost all data structures are developed around interfaces, which are the core of all data structures in Go language. An interface in Go is really a collection of methods, and working with Gomock allows us to write code that is easy to test. It’s hard to directly perceive interfaces except in use scenarios like reflection (although most people don’t perceive them when they use reflection), but in order to understand Go, you need to have a good understanding of interfaces. Next, we’ll learn about interfaces in Go from the data structure of the interface, how the structure is transformed into an interface, and the dynamically distributed implementation in Go.

The paper

Before we take the veil off interface, let’s take a look at the benefits of using interfaces in development. When it comes to interfaces, we have to mention the dependency inversion principle in object-oriented design, which was first proposed by Robert c. Martin, president of Object Mentor, in his article published in C++ Report in 1996. The original definition of dependency inversion was:

High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions

The core idea is to program for interfaces, not implementations. Because in software design, details are polygons, and well-designed layers of abstraction are more stable, an architecture based on abstraction is much more stable than one based on detail. The concept of interfaces exists in object-oriented programming languages such as Java and C#. In Java, for example, in addition to defining method signatures, interfaces in Java can define variables that can be used directly in classes that implement the interface.

public interface HumanInterface{
    public String name="test";
    public void eat();
}

public class Man implements HumanInterface{
    public void eat(){
        System.out.println(name+"eat a lot")
    }
}
public class Women implements HumanInterface{
    public void eat(){
        System.out.println(name+"eat very little")
    }
}
Copy the code

Java classes must explicitly declare the interface to implement, but in Go the interface is implicitly implemented, only need to implement all the methods defined in the interface and implement the interface.

The data structure

use

type myinterface interface {
	Func1() string
	Func2() string
}
type MyStruct struct {
}
func (m *MyStruct) Func1() string {
	return fmt.Sprintf("Func1 implement")
}
func (m *MyStruct) Func2() string {
	return fmt.Sprintf("Func2 implement")
}
Copy the code

From the above code we can see that there is no myInterface in the implementation of MyStruct, just as the interface implementation of Go language mentioned above is implicit. If we remove the implementation of the Func2 method from the above implementation, Compilation errors do not occur if the assignment of variables (of type myInterface), passing parameters (receiver myInterface), and returning parameters (return parameters of type myInterface) are not involved in the actual usage code. This is because the Go language only checks if the type implements the corresponding interface in these three cases.

Iface and eface

Interfaces in Go are divided into two types: an interface that contains a set of methods and an empty interface. The SRC/Runtime /runtime2.go file uses iface and eface constructs respectively. An empty interface is a special form of an interface type that has no methods, so no type needs to implement an empty interface. From an implementation point of view, any value satisfies the requirements of this interface. Therefore, a null interface type can hold any value, or it can cast the original value from the null interface. Both types of interfaces are declared as interfaces. But because empty interfaces are so common in the Go language, they are implemented using special types.

Type iface struct {TAB *itab data unsafe.Pointer} type itab struct {inter * interfaceType // The type information defined by the interface Hash uint32 // Copy of type.hash. Used for type switches. _ [4] Byte Fun [1] Uintptr // List of interface methods, that is, address list of functions, } type eface struct {_type *_type // type data unsafe.Pointer // Pointer to underlying data} type _type struct {size uintptr // Store the amount of memory required by the type, Ptrdata Uintptr // size of memory prefix holding all Pointers Hash uint32 Tflag Tflag Tags FieldAlign uint8 // the uint8 structure is used as a field to align the kind uint8 // Function for comparing objects of this type // (PTR to object A, PTR to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool // gcdata stores the GC type data for the garbage collector. // If the KindGCProg bit is set in kind, gcdata is a GC program. // Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte str nameOff PtrToThis typeOff //nameOff and typeOff are int32 values that the linker is responsible for embedding relative to the executable's meta information offset. The meta information will be loaded into the Runtime.moduleData structure (SRC/Runtime /symtab.go) during runtime. The Runtime provides a number of helper functions to help you find offsets relative to moduleData, ResolveNameOff (SRC /runtime/type.go) and resolveTypeOff (SRC /runtime/type.go)}Copy the code

The _type and ITAB fields are explained in the code above, but we don’t need to know what each field is for, just a general idea.

The _type structure is relatively simple, there is not much to say, I believe readers can easily understand by referring to the comments. So let’s talk about the ITAB structure. First, itAB has interfaceType in addition to the _type field. Interfacetype literally represents the current interfacetype, so the _type must correspond to the type information of the value pointed to by the interface.

Hash is a copy of the _type. Hash. Fun holds Pointers to the functions that make up the interface virtual table, so the number of elements held by Fun is dependent on the specific type and cannot be set to a fixed size.

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

type imethod struct {
	name nameOff
	ityp typeOff
}
Copy the code

SRC/Runtime /type.go/pkgPATH/MHDR/pkgPATH/MHDR/pkGPATH/MHDR/pkGPATH/MHDR Similarly, mapType, ArrayType, chanType, etc. are defined in the type.go file, which can be interpreted as the external performance information of the Go language type runtime.

How does a variable become an interface

We looked at the interface data structures in the previous section. Now let’s look at how they are initialized using the following code

func main(){
	var temp myinterface = MyStruct{ID:1}
	temp.Func1()

}
type myinterface interface {
	Func1() string
	Func2() string
}

type MyStruct struct {
	ID int64
	ptr *int64
}
//go:noinline
func (m MyStruct) Func1() string {
	return fmt.Sprintf("Func1 implement")
}
//go:noinline
func (m MyStruct) Func2() string {
	return fmt.Sprintf("Func2 implement")
}
Copy the code

Run the go tool compile-n -s -l test.go command to view the generated assembly code. Var temp myInterface = MyStruct{ID:1} The generated assembly code is as follows

0x0024 00036 (test.go:8) PCDATA $0, $0 0x0024 00036 (test.go:8) PCDATA $1, $1 0x0024 00036 (test.go:8) XORPS X0, X0 0x0027 00039 (test.go:8) MOVUPS X0, "".. autotmp_1+48(SP) 0x002c 00044 (test.go:8) MOVQ $1, "".. autotmp_1+48(SP) 0x0035 00053 (test.go:8) PCDATA $0, $1 0x0035 00053 (test.go:8) LEAQ go.itab."".MyStruct,"".myinterface(SB), AX 0x003c 00060 (test.go:8) PCDATA $0, $0 0x003c 00060 (test.go:8) MOVQ AX, (SP) 0x0040 00064 (test.go:8) PCDATA $0, $1 0x0040 00064 (test.go:8) PCDATA $1, $0 0x0040 00064 (test.go:8) LEAQ "".. autotmp_1+48(SP), AX 0x0045 00069 (test.go:8) PCDATA $0, $0 0x0045 00069 (test.go:8) MOVQ AX, 8(SP) 0x004a 00074 (test.go:8) CALL runtime.convT2I(SB) 0x004f 00079 (test.go:8) PCDATA $0, $1 0x004f 00079 (test.go:8) MOVQ 24(SP), AX 0x0054 00084 (test.go:8) MOVQ 16(SP), CX 0x0059 00089 (test.go:8) PCDATA $1, $2 0x0059 00089 (test.go:8) MOVQ CX, "".temp+32(SP) 0x005e 00094 (test.go:8) PCDATA $0, $0 0x005e 00094 (test.go:8) MOVQ AX, "".temp+40(SP)Copy the code

Divide the process into three parts

1. Allocate space

MOVQ $1, "".. autotmp_1+48(SP) ... LEAQ "".. autotmp_1+48(SP), AX MOVQ AX, 8(SP)Copy the code

1 corresponds to the ID of MyStruct, which is stored at the bottom-up +48 offset of the current stack frame. The compiler can then reference it with an address based on where it is stored.

2. Create itab

 LEAQ    go.itab."".MyStruct,"".myinterface(SB), AX
 MOVQ    AX, (SP)
Copy the code

It looks like the compiler has already created the necessary ITAB to represent iFace ahead of time and provided it to us through global symbols. The reason why the compiler does this is obvious. After all, no matter how many iFaces < myInterface,MyStruct> are created at runtime, all you need is an ITAB, and as defined in the ITAB, it has nothing to do with the variables initialized at runtime. The go.itab.””.MyStruct,””. Myinterface symbols will not be further explored in this article

3. Allocate data

CALL    runtime.convT2I(SB)
MOVQ    24(SP), AX
MOVQ    16(SP), CX
Copy the code

MyStruct,””.myInterface. 8 (SP) holds the address of the variable. The above two Pointers are passed as arguments to the convT2I function, which creates and returns the interface. src/runtime/iface.go

func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
	t := tab._type
	if raceenabled {
		raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
	}
	if msanenabled {
		msanread(elem, t.size)
	}
	x := mallocgc(t.size, t, true)
	typedmemmove(t, x, elem)
	i.tab = tab
	i.data = x
	return
}
Copy the code

The code above does four things:

  1. It creates a structure I for iFace.
  2. It assigns the value we just assigned to i.tab to the ITab pointer.
  3. It assigns a new object i.tab._type on the heap, and copies the value pointed to by the second parameter elem to the new object.
  4. Return the last interface.

Now we finally have the complete interface

Dynamic distribution implementation

Here is the first line of instantiated assembly code

MOVQ $1, "".. autotmp_1+48(SP) LEAQ go.itab."".MyStruct,"".myinterface(SB), AX MOVQ AX, (SP) LEAQ "".. autotmp_1+48(SP), AX MOVQ AX, 8(SP) CALL runtime.convT2I(SB) MOVQ 24(SP), AX MOVQ 16(SP), CX MOVQ CX, "".temp+32(SP) MOVQ AX, "".temp+40(SP)Copy the code

This is followed by assembly code for indirect calls to the method

MOVQ    "".temp+32(SP), AX
MOVQ    24(AX), AX
MOVQ    "".temp+40(SP), CX
MOVQ    CX, (SP)
CALL    AX
Copy the code

AX holds a pointer to itab, which is actually a pointer to go.itab.””.mystruct,””.myInterface. We can find itab.fun. We can find itab.fun. And we already know that fun[0] actually points to a pointer to main.(MyStruct).func1. Since the method itself has no parameters, all it needs to do is pass in the receiver and complete the function CALL with the CALL instruction.

If we modify the code to look like this

temp.Func2()
Copy the code

This is to look at the assembly code, and the original is different

MOVQ    "".temp+32(SP), AX
MOVQ    32(AX), AX
MOVQ    "".temp+40(SP), CX
MOVQ    CX, (SP)
CALL    AX
Copy the code

It’s easy to see that the function pointer it gets has an 8-byte offset from the first one, which is easy to understand because the fun field is lexicographically sorted from the list of interface method implementations.