iface
and eface
are both underlying structures describing interfaces in Go. The difference is that iface
the interface described by contains methods, while eface
is 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
}
iface
Two pointers are maintained internally, tab
pointing to an itab
entity, which represents the type of the interface and the entity type assigned to this interface. data
It points to the specific value of the interface, generally a pointer to heap memory.
Let’s take a closer look at itab
the structure: _type
the fields describe the type of entity, including memory alignment, size, etc.; inter
the fields describe the type of interface. fun
Field 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 fun
the 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 interfacetype
the type, which describes the type of interface:
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
As you can see, it wraps _type
the type, _type
which is actually a structure describing various data types in the Go language. We noticed that there is also a mhdr
field here, which represents the list of functions defined by the interface and pkgpath
records the name of the package that defines the interface.
Here’s a picture to look at iface
the overall structure:
Let’s take a look at eface
the source code:
type eface struct {
_type *_type
data unsafe.Pointer
}
In comparison iface
, eface
it is relatively simple. Only one field is maintained _type
, indicating the specific entity type carried by the empty interface. data
Describes specific values.
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 iface
and eface
structure can be linked: both functions combine the parameters 组装
to form the final interface.
As a supplement, let’s finally look at _type
the 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 _type
managed 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 Typing
Duck 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_world
and 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 python
one 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, php
objects 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, php
convert 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.