Go language interview essence - what is the difference between iface and eface? What is the relationship between Go language and duck typing?

ifaceand efaceare both underlying structures describing interfaces in Go. The difference is that ifacethe interface described by contains methods, while efaceis an empty interface that does not contain any methods: interface{}.

Take a look at the source code level:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter  *interfacetype
	_type  *_type
	link   *itab
	hash   uint32 // copy of _type.hash. Used for type switches.
	bad    bool   // type does not implement interface
	inhash bool   // has this itab been added to hash?
	unused [2]byte
	fun    [1]uintptr // variable sized
}

ifaceTwo pointers are maintained internally, tabpointing to an itabentity, which represents the type of the interface and the entity type assigned to this interface. dataIt points to the specific value of the interface, generally a pointer to heap memory.

Let’s take a closer look at itabthe structure: _typethe fields describe the type of entity, including memory alignment, size, etc.; interthe fields describe the type of interface. funField placement and the method address of the specific data type corresponding to the interface method implement dynamic dispatch of the interface calling method. Generally, this table will be updated every time a conversion occurs when assigning values ​​to the interface, or the cached itab will be obtained directly.

Only methods related to entity types and interfaces are listed here. Other methods of entity types will not appear here. If you have learned C++, you can analogize the concept of virtual functions here.

In addition, you may wonder why funthe size of the array is 1. What if the interface defines multiple methods? In fact, what is stored here is the function pointer of the first method. If there are more methods, they will continue to be stored in the memory space after it. From an assembly point of view, these function pointers can be obtained by adding addresses, with no impact. Incidentally, these methods are arranged lexicographically by function name.

Take another look at interfacetypethe type, which describes the type of interface:

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

As you can see, it wraps _typethe type, _typewhich is actually a structure describing various data types in the Go language. We noticed that there is also a mhdrfield here, which represents the list of functions defined by the interface and pkgpathrecords the name of the package that defines the interface.

Here’s a picture to look at ifacethe overall structure:

Insert image description here

Let’s take a look at efacethe source code:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

In comparison iface, efaceit is relatively simple. Only one field is maintained _type, indicating the specific entity type carried by the empty interface. dataDescribes specific values.

Insert image description here

Let's look at an example:

package main

import "fmt"

func main() {
	x := 200
	var any interface{} = x
	fmt.Println(any)

	g := Gopher{"Go"}
	var c coder = g
	fmt.Println(c)
}

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p Gopher) debug() {
	fmt.Printf("I am debuging %s language\n", p.language)
}

Execute the command and print out the assembly language:

go tool compile -S ./src/main.go

As you can see, two functions are called in the main function:

func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

The parameters of the above two functions and the fields of the ifaceand efacestructure can be linked: both functions combine the parameters 组装to form the final interface.

As a supplement, let’s finally look at _typethe structure:

type _type struct {
    // 类型大小
	size       uintptr
    ptrdata    uintptr
    // 类型的 hash 值
    hash       uint32
    // 类型的 flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldalign uint8
    // 类型的编号,有bool, slice, struct 等等等等
	kind       uint8
	alg        *typeAlg
	// gc 相关
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

Various data types in the Go language are _typemanaged by adding some additional fields on the basis of fields:

type arraytype struct {
	typ   _type
	elem  *_type
	slice *_type
	len   uintptr
}

type chantype struct {
	typ  _type
	elem *_type
	dir  uintptr
}

type slicetype struct {
	typ  _type
	elem *_type
}

type structtype struct {
	typ     _type
	pkgPath name
	fields  []structfield
}

The structure definitions of these data types are the basis for reflection implementation.

The relationship between Go language and duck typing

Let’s look directly at the definition in Wikipedia:

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Translated: If something looks like a duck, swims like a duck, and quacks like a duck, then it can be considered a duck.

Duck TypingDuck typing is an object inference strategy for dynamic programming languages. It pays more attention to how objects can be used rather than the type of the object itself. As a static language, Go language perfectly supports duck typing through interfaces.

For example, in the dynamic language python, define a function like this:

def hello_world(coder):
    coder.say_hello()

When calling this function, any type can be passed in, as long as it implements say_hello()the function. If it is not implemented, errors will occur during operation.

In static languages ​​such as Java and C++, you must explicitly declare that you implement an interface before it can be used wherever this interface is needed. If you call a function in a program hello_worldand pass in a say_hello()type that is not implemented at all, it will not pass during the compilation phase. This is why static languages ​​are safer than dynamic languages.

The difference between dynamic languages ​​and static languages ​​is reflected here. Static languages ​​can detect type mismatch errors during compilation, unlike dynamic languages, which must run to that line of code before an error is reported. By the way, this is also pythonone of the reasons why I don't like to use . Of course, static languages ​​require programmers to write programs according to regulations during the coding stage and specify data types for each variable. This, to a certain extent, increases the workload and lengthens the amount of code. Dynamic languages ​​do not have these requirements, allowing people to focus more on business, the code is shorter, and it is faster to write. This is clear to students who write Python.

As a modern static language, Go language has the advantage of being a latecomer. It introduces the convenience of dynamic language and at the same time performs type checking of static language. It is very happy to write. Go adopts a compromise approach: it does not require the type to explicitly declare that it implements an interface. As long as the relevant methods are implemented, the compiler can detect it.

Let’s see an example:

First define an interface and a function that uses this interface as a parameter:

type IGreeting interface {
	sayHello()
}

func sayHello(i IGreeting) {
	i.sayHello()
}

Let’s define two structures:

type Go struct {}
func (g Go) sayHello() {
	fmt.Println("Hi, I am GO!")
}

type PHP struct {}
func (p PHP) sayHello() {
	fmt.Println("Hi, I am PHP!")
}

Finally, call the sayHello() function in the main function:

func main() {
	golang := Go{}
	php := PHP{}

	sayHello(golang)
	sayHello(php)
}

Program output:

Hi, I am GO!
Hi, I am PHP!

In the main function, when calling the sayHello() function, golang, phpobjects are passed in. They do not explicitly declare to implement the IGreeting type, but only implement the sayHello() function specified by the interface. In fact, when the compiler calls the sayHello() function, it will implicitly golang, phpconvert the object into the IGreeting type, which is also the type checking function of static languages.

By the way, let’s mention the characteristics of dynamic languages:

The type of variable binding is undefined and can only be determined during runtime.
Functions and methods can receive parameters of any type, and the parameter type is not checked when calling.
There is no need to implement an interface.

To summarize, duck typing is a style of dynamic languages ​​in which the valid semantics of an object are not determined by inheriting from a specific class or implementing a specific interface, but by its "current set of methods and properties" Decide. Go, as a static language, is implemented through interfaces 鸭子类型. In fact, Go's compiler does hidden conversion work in it.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132795657