This article is still very long, basically covers all aspects of interface, there are examples, source code analysis, assembly analysis, write more than 20 days. There are still some things that are not covered, for example, reflection is not covered in the article, of course, I will write a separate article on reflection later, that is the next part.
I still hope you can gain something after reading this article. If you have any questions or suggestions, please leave a message at the back of this article.
The structure of this article is relatively simple. It directly raises 10 questions and answers one by one.
1. The relationship between Go language and duck type
Let’s get straight to the definition from Wikipedia:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
Translation: If something looks like a duck, swims like a duck, quacks like a duck, it can be considered a duck.
Duck Typing, Duck Typing, is an object inference strategy for dynamic programming languages that focuses more on how objects can be used than on the types of objects themselves. Go is a static language that perfectly supports duck types through interfaces.
For example, in the dynamic language Python, define a function like this:
def hello_world(coder):
coder.say_hello()
Copy the code
When you call this function, you can pass in any type, as long as it implements the say_hello() function. If it is not implemented, an error occurs during the run.
In static languages such as Java and C++, an interface must be explicitly declared to be implemented before it can be used wherever it is needed. If you call hello_world in a program and pass in a type that does not implement say_hello() at all, it will not pass at compile time. This is why static languages are safer than dynamic ones.
This is where the difference between dynamic and static languages comes in. Static languages can detect type mismatches at compile time, unlike dynamic languages, which have to run to that line of code to report an error. By the way, this is one of the reasons I don’t like python. Of course, static languages require programmers to write programs according to rules during the coding phase, specifying data types for each variable, which, to some extent, increases the amount of work and the amount of code. Dynamic languages don’t have these requirements, allowing you to focus on the business, and the code is shorter and faster to write, as those of you who write Python know.
As a modern static language, Go has the advantage of being a late mover. It introduces the convenience of dynamic languages, while doing type checking for static languages, and is very Happy to write. Go takes a middle ground: it doesn’t require a type to explicitly declare that an interface is implemented, as long as the associated methods are implemented, and the compiler can detect them.
Here’s an example:
Define an interface and a function that takes this interface as an argument:
type IGreeting interface {
sayHello()
}
func sayHello(i IGreeting) {
i.sayHello()
}
Copy the code
Let’s define two more 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!" )
}
Copy the code
Finally, call sayHello() inside main:
func main() {
golang := Go{}
php := PHP{}
sayHello(golang)
sayHello(php)
}
Copy the code
Program output:
Hi, I am GO!
Hi, I am PHP!
Copy the code
In main, when sayHello() is called, golang, PHP objects are passed in. They do not explicitly declare that they implement the IGreeting type, only the sayHello() function specified by the interface. In fact, the compiler implicitly converts a Golang PHP object to IGreeting when it calls sayHello(), which is a type checking feature of static languages.
By the way, a few more features of dynamic languages:
The type of the variable binding is indeterminate, it is only at run time that functions and methods can accept arguments of any type, and it is not necessary to implement an interface when calling without checking parameter types
To summarize, a duck type is a dynamic language style in which an object’s valid semantics are determined not by inheriting from a particular class or implementing a particular interface, but by its “current collection of methods and attributes.” Go is a static language that implements the duck type through the interface, where the Go compiler actually does the implicit conversion.
2. Difference between value receiver and pointer receiver
methods
Method can add new behavior to user-defined types. The difference between a method and a function is that a method has a receiver. Add a receiver to a function and it becomes a method. The receiver can be either a value receiver or a pointer receiver.
When a method is called, the value type can call either the value receiver’s method or the pointer receiver’s method. Pointer types can call either the pointer receiver’s method or the value receiver’s method.
That is, values and Pointers of that type can be called regardless of the recipient of a method, and do not have to conform strictly to the recipient’s type.
Here’s an example:
package main
import "fmt"
type Person struct {
age int
}
func (p Person) howOld() int {
return p.age
}
func (p *Person) growUp() {
p.age += 1
}
func main() {
// qcrao is a value type
qcrao := Person{age: 18}
// A value type calls a method whose receiver is also a value type
fmt.Println(qcrao.howOld())
// A value type calls a method whose receiver is a pointer type
qcrao.growUp()
fmt.Println(qcrao.howOld())
// ----------------------
// stefno is a pointer type
stefno := &Person{age: 100}
// The pointer type calls a method whose receiver is a value type
fmt.Println(stefno.howOld())
// A pointer type calls a method whose receiver is also a pointer type
stefno.growUp()
fmt.Println(stefno.howOld())
}
Copy the code
The output of the above example is:
18
19
100
101
Copy the code
When growUp is called, its Age value changes regardless of whether the caller is of a value type or a pointer type.
In fact, when the receiver types of the type and method are different, the compiler actually does some work behind the scenes, rendering it in a table:
– |
Value of the receiver |
Pointer receiver |
Value type caller |
Method uses a copy of the caller, similar to “pass values” |
Call a method using a reference to a value. In the above example, qcrao.growup () is actually (&qcrao.growup ()). |
Pointer type caller |
Pointers are dereferenced as values. In the above example, stefno. HowOld () is actually (*stefno).howold (). |
This is also called “passing”, where operations in a method affect the caller, similar to pointer passing, where a pointer is copied |
Value receiver and pointer receiver
As mentioned earlier, regardless of whether the receiver type is a value type or a pointer type, it can be called by either a value type or a pointer type, which actually works through syntactic sugar.
Conclusion: implementing a method whose receiver is a value type automatically implements a method whose receiver is a pointer type. Methods that implement a receiver type of pointer do not automatically generate methods that correspond to a receiver type of value.
Take a look at an example to make it perfectly clear:
package main
import "fmt"
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)
}
func main() {
var c coder = &Gopher{"Go"}
c.code()
c.debug()
}
Copy the code
An interface coder is defined in the above code. The interface defines two functions:
code()
debug()
Copy the code
Then define a structure Gopher, it implements two methods, a value receiver, a pointer receiver.
Finally, we call the two defined functions in main using variables of interface type.
Run it and the result is:
I am coding Go language
I am debuging Go language
Copy the code
But if we change the first statement of main:
func main() {
var c coder = Gopher{"Go"}
c.code()
c.debug()
}
Copy the code
Run it and report an error:
./main.go:24:6: cannot use Programmer literal (type Programmer) as type coder in assignment:
Programmer does not implement coder (debug method has pointer receiver)
Copy the code
See the difference between these two pieces of code? The first time is to assign &gopher to coder; The second time is to assign Gopher to coder.
Gopher does not implement coder. Obviously, the Gopher type doesn’t implement debug methods; On the surface, Gopher types don’t implement code methods either, but because Gopher types implement code methods, they let Gopher types automatically have code methods.
Of course, there is a simple explanation for this: The receiver is a pointer type method, and it is likely that changes to the receiver’s properties will occur in the method, affecting the receiver; For methods where the receiver is a value type, there is no effect on the receiver itself in the method.
So, when you implement a method whose receiver is a value type, you can automatically generate a method whose receiver is a pointer type, because neither affects the receiver. However, when a method whose receiver is a pointer type is implemented, if at this point a method whose receiver is a value type is automatically generated, the expected change to the receiver (implemented through the pointer) cannot be implemented because the value type makes a copy and doesn’t really affect the caller.
Finally, just remember this:
If you implement a method whose receiver is a value type, you implicitly also implement a method whose receiver is a pointer type.
When the two are used
If the receiver of a method is a value type, whether the caller is an object or an object pointer, the modification is a copy of the object and does not affect the caller. If the receiver of a method is a pointer type, the caller modifies the object to which the pointer points.
Reasons to use Pointers as method recipients:
• Method can modify the value pointed to by the receiver.
• Avoid copying the value every time a method is called, which is more efficient when the value type is a large structure.
Whether to use a value receiver or a pointer receiver is not determined by whether the method modifies the caller (that is, the receiver), but should be based on the nature of the type.
If the type is “primitive in nature,” that is, its members are made up of primitive types built into the Go language, such as strings, integer values, etc., then define a method for the value receiver type. The built-in reference types, such as Slice, map, interface, and Channel, are special. When you declare them, you actually create a header, which is also a direct way to define the recipient type of the value. In this way, when you call a function, you copy these types of headers directly, and the headers themselves are designed for copying.
If the type is nonprimitive in nature and cannot be safely copied, and should always be shared, define methods for pointer receivers. For example, struct files in the go source code should not be copied, there should be only one entity.
This paragraph is quite roundabout, you can Go to see “Go Language Practice” 5.3 that section.
3. What is the difference between iFace and eface
Iface and eface are both low-level constructs of Go describing interfaces. The difference is that iFace describes interfaces that contain methods, while eface is an empty interface that contains no methods: interface{}.
Take a look at the source code:
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
}
Copy the code
Iface maintains two Pointers internally, TAB pointing to an ITAB entity that represents the type of the interface and the entity type assigned to the interface. Data points to an interface-specific value, generally a pointer to heap memory.
The _type field describes the type of the entity, including memory alignment, size, and so on. The Inter field describes the type of the interface. The fun field places the method address of the specific data type corresponding to the interface method, implementing dynamic dispatch of the interface calling method, generally updating the table every time the interface assignment is converted, or directly fetching the cached ITAB.
Only methods related to the entity type and interface are listed here; other methods of the entity type are not listed here. If you’ve learned C++, this is an analogy to virtual functions.
Also, you might be wondering why the fun array is 1 in size, but what if the interface defines multiple methods? In fact, the function pointer to the first method is stored here, and if there are more methods, the memory space after it is stored. From an assembly point of view, these function Pointers can be obtained by adding addresses, which doesn’t matter. Incidentally, these methods are sorted in lexicographical order by function names.
Consider the interfaceType type, which describes the type of the interface:
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
Copy the code
As you can see, it wraps the _type type, which is actually a structure that describes the various data types in the Go language. We notice that there is also an MHDR field that represents the list of functions defined by the interface, and the PKgPATH record defines the package name of the interface.
Here’s an overview of the iFace structure:
Here is the eface source code:
type eface struct {
_type *_type
data unsafe.Pointer
}
Copy the code
Compared to iFace, eFace is relatively simple. Only one _type field is maintained, representing the specific entity type hosted 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)
}
Copy the code
Execute command to print assembly language:
go tool compile -S ./src/main.go
Copy the code
As you can see, two functions are called in main:
func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)
Copy the code
The arguments of the above two functions are related to the fields of the iFace and eface structures: both functions assemble the arguments to form the final interface.
As a final supplement, let’s look at the _type structure:
type _type struct {
// Type size
size uintptr
ptrdata uintptr
// Hash value of type
hash uint32
// Type of flag, related to reflection
tflag tflag
// Memory alignment is related
align uint8
fieldalign uint8
// Type numbers such as bool, slice, struct, etc
kind uint8
alg *typeAlg
/ / gc
gcdata *byte
str nameOff
ptrToThis typeOff
}
Copy the code
All data types in Go language are managed by adding some additional fields on the basis of the _type field:
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
}
Copy the code
The structural definition of these data types is the basis for reflection implementation.
4. Dynamic type and value of the interface
Iface contains two fields: TAB is the interface table pointer, pointing to the type information; Data is a data pointer that points to specific data. They are called dynamic types and dynamic values, respectively. Interface values include dynamic types and dynamic values.
Interface types are compared to nil
A zero value for an interface value means that both the dynamic type and dynamic value are nil. The interface value is said to be interface value == nil only if and only if both parts are nil.
Here’s an example:
package main
import "fmt"
type Coder interface {
code()
}
type Gopher struct {
name string
}
func (g Gopher) code() {
fmt.Printf("%s is coding\n", g.name)
}
func main() {
var c Coder
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
var g *Gopher
fmt.Println(g == nil)
c = g
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
}
Copy the code
Output:
true
c: <nil>, <nil>
true
false
c: *main.Gopher, <nil>
Copy the code
When g is assigned to C, the dynamic type of C is *main.Gopher. The dynamic type of C is still nil, but when c is compared to nil, the result is false.
Take a look at the output of an example:
package main
import "fmt"
type MyError struct {}
func (i MyError) Error() string {
return "MyError"
}
func main() {
err := Process()
fmt.Println(err)
fmt.Println(err == nil)
}
func Process() error {
var err *MyError = nil
return err
}
Copy the code
Function running result:
<nil>
false
Copy the code
Here first defined a MyError structure, the implementation of the Error function, also the implementation of the Error interface. The Process function returns an error interface that implies type conversions. So even though it’s nil, it’s actually of type *MyError, and when you compare it to nil, it’s false.
[Extension 3] How to print the dynamic type and value of the interface?
Look directly at the code:
package main
import (
"unsafe"
"fmt"
)
type iface struct {
itab, data uintptr
}
func main() {
var a interface{} = nil
var b interface{} = (*int)(nil)
x := 5
var c interface{} = (*int)(&x)
ia := *(*iface)(unsafe.Pointer(&a))
ib := *(*iface)(unsafe.Pointer(&b))
ic := *(*iface)(unsafe.Pointer(&c))
fmt.Println(ia, ib, ic)
fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}
Copy the code
An iFace structure is directly defined in the code, and two Pointers are used to describe ITAB and data. After that, the contents of A, B and C in memory are forced to be interpreted as our customized IFace. Finally, you can print out the address of the dynamic type and dynamic value.
The running results are as follows:
{0 0} {17426912 0} {17426912 842350714568}
5
Copy the code
The address of both the dynamic type and dynamic value of A is 0, that is, nil; The dynamic type of b is the same as the dynamic type of C. Finally, the dynamic value of C is 5.
5. The compiler automatically detects whether the type implements the interface
It’s common to see open source libraries with strange uses like the following:
var _ io.Writer = (*myWriter)(nil)
Copy the code
It’s a bit confusing at this point, not knowing what the author is trying to do, but this is actually the answer to the question. The compiler checks to see if the *myWriter type implements the IO.Writer interface.
Here’s an example:
package main
import "io"
type myWriter struct {
}
/*func (w myWriter) Write(p []byte) (n int, err error) {
return
} * /
func main() {
// Check whether the *myWriter type implements the IO.Writer interface
var _ io.Writer = (*myWriter)(nil)
// Check whether the myWriter type implements the IO.Writer interface
var _ io.Writer = myWriter{}
}
Copy the code
After commenting out the Write function defined for myWriter, run the program:
src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:
*myWriter does not implement io.Writer (missing Write method)
src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:
myWriter does not implement io.Writer (missing Write method)
Copy the code
MyWriter /myWriter does not implement the IO.Writer interface, that is, the Write method is not implemented.
After uncomment, run the program without error.
In fact, the assignment is implicitly converted, and during the conversion the compiler checks whether the type to the right of the equals sign implements the function specified by the interface to the left of the equals sign.
To sum up, you can check if a type implements an interface by adding something like the following to your code:
var _ io.Writer = (*myWriter)(nil)
var _ io.Writer = myWriter{}
Copy the code
6. What is the interface construction process
We have looked at the source code for iface and eface and know that the most important ones for iFace are itab and _type.
In order to understand how interfaces are constructed, I will take up the weapons of assembly and restore the truth behind them.
Take a look at an example code:
package main
import "fmt"
type Person interface {
growUp()
}
type Student struct {
age int
}
func (p Student) growUp() {
p.age += 1
return
}
func main() {
var qcrao = Person(Student{age: 18})
fmt.Println(qcrao)
}
Copy the code
Execute command:
go tool compile -S ./src/main.go
Copy the code
The assembly code for main is as follows:
0x0000 00000 (./src/main.go:30) TEXT "".main(SB), $80-0
0x0000 00000 (./src/main.go:30) MOVQ (TLS), CX
0x0009 00009 (./src/main.go:30) CMPQ SP, 16(CX)
0x000d 00013 (./src/main.go:30) JLS 157
0x0013 00019 (./src/main.go:30) SUBQ $80, SP
0x0017 00023 (./src/main.go:30) MOVQ BP, 72(SP)
0x001c 00028 (./src/main.go:30) LEAQ 72(SP), BP
0 x0021 00033 (. / SRC/main. Go: 30) FUNCDATA $0, gclocals · 69 c1753bd5f81501d95132d08af04464 (SB.)
0 x0021 00033 (. / SRC/main. Go: 30) FUNCDATA $1, gclocals · e226d4ae4a7cad8835311c6a4683c14f (SB.)
0x0021 00033 (./src/main.go:31) MOVQ $18, "".. autotmp_1+48(SP)
0x002a 00042 (./src/main.go:31) LEAQ go.itab."".Student,"".Person(SB), AX
0x0031 00049 (./src/main.go:31) MOVQ AX, (SP)
0x0035 00053 (./src/main.go:31) LEAQ "".. autotmp_1+48(SP), AX
0x003a 00058 (./src/main.go:31) MOVQ AX, 8(SP)
0x003f 00063 (./src/main.go:31) PCDATA $0, $0
0x003f 00063 (./src/main.go:31) CALL runtime.convT2I64(SB)
0x0044 00068 (./src/main.go:31) MOVQ 24(SP), AX
0x0049 00073 (./src/main.go:31) MOVQ 16(SP), CX
0x004e 00078 (./src/main.go:33) TESTQ CX, CX
0x0051 00081 (./src/main.go:33) JEQ 87
0x0053 00083 (./src/main.go:33) MOVQ 8(CX), CX
0x0057 00087 (./src/main.go:33) MOVQ $0, "".. autotmp_2+56(SP)
0x0060 00096 (./src/main.go:33) MOVQ $0, "".. autotmp_2+64(SP)
0x0069 00105 (./src/main.go:33) MOVQ CX, "".. autotmp_2+56(SP)
0x006e 00110 (./src/main.go:33) MOVQ AX, "".. autotmp_2+64(SP)
0x0073 00115 (./src/main.go:33) LEAQ "".. autotmp_2+56(SP), AX
0x0078 00120 (./src/main.go:33) MOVQ AX, (SP)
0x007c 00124 (./src/main.go:33) MOVQ $1, 8(SP)
0x0085 00133 (./src/main.go:33) MOVQ $1, 16(SP)
0x008e 00142 (./src/main.go:33) PCDATA $0, $1
0x008e 00142 (./src/main.go:33) CALL fmt.Println(SB)
0x0093 00147 (./src/main.go:34) MOVQ 72(SP), BP
0x0098 00152 (./src/main.go:34) ADDQ $80, SP
0x009c 00156 (./src/main.go:34) RET
0x009d 00157 (./src/main.go:34) NOP
0x009d 00157 (./src/main.go:30) PCDATA $0, $-1
0x009d 00157 (./src/main.go:30) CALL runtime.morestack_noctxt(SB)
0x00a2 00162 (./src/main.go:30) JMP 0
Copy the code
Let’s start at line 10. If you don’t understand the first few lines of assembly code, you can go back to the first two articles of the public account, which I omit here.
Assembly lines |
operation |
10-14 |
Construct arguments to the call runtime.convt2i64 (SB) |
Let’s look at the argument form of this function:
func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {
/ /...
}
Copy the code
ConvT2I64 constructs an Inteface, our Person interface.
The location of the first argument is (SP), which is assigned the address of go.itab.””.student,””.person (SB).
From the generated assembly we find:
go.itab."".Student,"".Person SNOPTRDATA dupok size=40
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0010 00 00 00 00 00 00 00 00 da 9f 20 d4
rel 0+8 t=1 type."".Person+0
rel 8+8 t=1 type."".Student+0
Copy the code
Size =40 Size =40 bytes, to recap:
type itab struct {
Inter * interfaceType // 8 bytes
_type *_type // 8 bytes
Link *itab // 8 bytes
Hash uint32 // 4 bytes
Bad bool // 1 byte
Inhash bool // 1 byte
Unused [2]byte // 2 bytes
Fun [1] Uintptr // variable sized // 8 bytes
}
Copy the code
Adding up the size of each field gives the ITAB structure a size of 40 bytes. Note that most of the numbers are zeros. The first four bytes da 9f 20 d4 are actually itab hash values, which are used to determine if the two types are the same.
The next two lines are linked instructions, which simply combine all the source files and assign each symbol a global location value. The first 8 bytes store the address of type.””.Person, which corresponds to the INTER field in itAB, indicating the interface type. Type.””.Student address, corresponding to the itab _type field, indicates the specific type.
The second argument is simpler, and is the address of the number 18, which is used to initialize the Student structure.
Assembly lines |
operation |
15 |
Call the runtime. ConvT2I64 (SB) |
Take a look at the code:
func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
/ /...
var x unsafe.Pointer
if *(*uint64)(elem) == 0 {
x = unsafe.Pointer(&zeroVal[0])
} else {
x = mallocgc(8, t, false)
*(*uint64)(x) = *(*uint64)(elem)
}
i.tab = tab
i.data = x
return
}
Copy the code
This code is relatively simple, assigning TAB to the TAB field of iFace; The data section allocates a chunk of memory on the heap and copies the 18 that elem points to. And then the iFace is assembled.
Assembly lines |
operation |
17 |
Assign i. TAB to CX |
18 |
Assign i. ata to AX |
19-21 |
Check if i.tabb is nil. If not, move CX by 8 bytes. That is, assign the _type field of ITab to CX, which is also the entity type of the interface, and will eventually be used as an argument to ftt. Println |
After that, we will call the FMT.Println function and prepare the parameters.
This completes the construction of an interface.
How do I print the Hash value of the interface type?
Here is a reference to cao Dashen translated an article, reference materials will be written. Specific practices are as follows:
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter uintptr
_type uintptr
link uintptr
hash uint32
_ [4]byte
fun [1]uintptr
}
func main() {
var qcrao = Person(Student{age: 18})
iface := (*iface)(unsafe.Pointer(&qcrao))
fmt.Printf("iface.tab.hash = %#x\n", iface.tab.hash)
}
Copy the code
A copycat version of iFace and ITAB is called copycat because some key data structures in ITAB are not specified, such as _type, which can be found by comparing the real definition, but the copycat version still works because _type is just a pointer.
In main, we construct an interface object, qcrao, cast it, and then read the hash value. You can try it yourself.
Running results:
iface.tab.hash = 0xd4209fda
Copy the code
It is worth mentioning that when CONSTRUCTING the interface Qcrao, even if I write age as something else, the hash value is still the same, which should be expected. The hash value is only related to its fields and methods.
7. The difference between casts and assertions
We know that implicit type conversions are not allowed in Go, which means you can’t have variables of different types on both sides of =.
Type conversions and type assertions are essentially the conversion of one type to another. The difference is that type assertions are operations on interface variables.
Type conversion
For type conversions, the two types must be compatible. The syntax for type conversions is:
< result type > := < target type > (< expression >)
package main
import "fmt"
func main() {
var i int = 9
var f float64
f = float64(i)
fmt.Printf("%T, %v\n", f, f)
F = 10.8,
a := int(f)
fmt.Printf("%T, %v\n", a, a)
// s := []int(i)
Copy the code
In the above code, I defined a variable of int and float64 and tried to convert them to each other. The result was successful: int and float64 are compatible.
If I comment out the last line of code, the compiler reports a type incompatibility error:
cannot convert i (type int) to type []int
Copy the code
assertions
As mentioned earlier, because the empty interface{} does not define any functions, all types in Go implement empty interfaces. When a function parameter is interface{}, you need to assert the parameter in the function to get its true type.
The syntax for assertions is:
// Secure type assertion
< value of target type >, < Boolean parameter > := < expression >.(Target type)
// Insecure type assertion
< value of target type > := < expression >.(Target type)
Conversion is similar to type assertion, except that type assertion is an operation on an interface.
Here’s a quick example:
package main
import "fmt"
type Student struct {
Name string
Age int
}
func main() {
var i interface{} = new(Student)
s := i.(Student)
fmt.Println(s)
}
Copy the code
Run it:
panic: interface conversion: interface {} is *main.Student, not main.Student
Copy the code
The assertion fails because I is *Student, not Student. Here panic occurs directly, and the online code may not be suitable for this, you can use the “security assertion” syntax:
func main() {
var i interface{} = new(Student)
s, ok := i.(Student)
if ok {
fmt.Println(s)
}
}
Copy the code
This way, there will be no panic even if the assertion fails.
Another form of assertion is used to determine the type of interface using switch statements. Each case is considered sequentially. When a case is hit, the statements in the case are executed, so the order of the case statements is important because it is likely that there will be multiple case matches.
A code example is as follows:
func main() {
//var i interface{} = new(Student)
//var i interface{} = (*Student)(nil)
var i interface{}
fmt.Printf("%p %v\n", &i, i)
judge(i)
}
func judge(v interface{}) {
fmt.Printf("%p %v\n", &v, v)
switch v := v.(type) {
case nil:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("nil type[%T] %v\n", v, v)
case Student:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("Student type[%T] %v\n", v, v)
case *Student:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("*Student type[%T] %v\n", v, v)
default:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("unknow\n")
}
}
type Student struct {
Name string
Age int
}
Copy the code
The main function has three different lines of declarations. Run one line at a time and comment the other two lines to get three sets of results:
// --- var i interface{} = new(Student)
0xc4200701b0 [Name: ], [Age: 0]
0xc4200701d0 [Name: ], [Age: 0]
0xc420080020 [Name: ], [Age: 0]
*Student type[*main.Student] [Name: ], [Age: 0]
// --- var i interface{} = (*Student)(nil)
0xc42000e1d0 <nil>
0xc42000e1f0 <nil>
0xc42000c030 <nil>
*Student type[*main.Student] <nil>
// --- var i interface{}
0xc42000e1d0 <nil>
0xc42000e1e0 <nil>
0xc42000e1f0 <nil>
nil type[<nil>] <nil>
Copy the code
For the first line:
var i interface{} = new(Student)
Copy the code
I is of type *Student and matches the third case. From the three addresses printed, the variables in all three are actually different. There’s a local variable I in main; When YOU call a function, you’re actually copying a copy of the arguments, so you have another variable v in the function, which is a copy of I; After assertion, a new copy is made. So the addresses of the three variables that are printed are all different.
For the second line:
var i interface{} = (*Student)(nil)
Copy the code
So the point here is that I is dynamically typed (*Student), it’s nil, it’s not of type nil, and when you compare it to nil, you get false.
The last line of statements:
var i interface{}
Copy the code
This time I is nil.
FMT.Println takes the parameter interface. For built-in types, the function uses internal enumeration to derive its true type, which is then converted to string printing. For custom types, we first determine whether the type implements the String() method. If so, we print the result of the String() method directly. Otherwise, it prints by iterating through the members of the object through reflection.
Let’s take a quick example. It’s easy, don’t get nervous:
package main
import "fmt"
type Student struct {
Name string
Age int
}
func main() {
var s = Student{
Name: "qcrao",
Age: 18,
}
fmt.Println(s)
}
Copy the code
Since the Student structure does not implement the String() method, fmt.println prints member variables one by one using reflection:
{qcrao 18}
Copy the code
Add an implementation of the String() method:
func (s Student) String() string {
return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}
Copy the code
Print result:
[Name: qcrao], [Age: 18]
Copy the code
According to our custom method to print.
【 参 考 译 文 2】 For the above example, if you change it:
func (s *Student) String() string {
return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}
Copy the code
The Student structure now has only one String() function whose receiver type is pointer, printing the result:
{qcrao 18}
Copy the code
Why is that?
Methods of type T where only the receiver is T; While type *T has methods whose recipients are T and *T. The syntactic way in which T can be modulated directly to *T is just the syntactic sugar of Go.
So, when the Student structure defines a String() method whose recipient type is a value type, it passes
fmt.Println(s)
fmt.Println(&s)
Copy the code
Can be printed in a custom format.
If the Student structure defines a String() method whose recipient type is a pointer, it will only pass
fmt.Println(&s)
Copy the code
To print in a custom format.
8. Principle of interface conversion
As you can see from the aforementioned iFace source code, it actually contains the type of the interface, InterfaceType, and the type _type of the entity type, both of which are members of the IFace field ITab. That is, generating an ITAB requires both the interface type and the entity type.
<interface type, entity type > ->itable
When determining whether a type satisfies an interface, Go matches the method set of the type with the method set required by the interface. If the method set of the type completely contains the method set of the interface, the type can be considered to implement the interface.
For example, if a certain type has M methods and an interface has N methods, it is easy to know that the time complexity of this judgment is O(Mn). Go will sort the functions of the method set according to the lexicographical order of function names, so the actual time complexity is O(m+ N).
Here we explore the principles behind converting one interface to another, of course, because of type compatibility.
Let’s just look at an example:
package main
import "fmt"
type coder interface {
code()
run()
}
type runner interface {
run()
}
type Gopher struct {
language string
}
func (g Gopher) code() {
return
}
func (g Gopher) run() {
return
}
func main() {
var c coder = Gopher{}
var r runner
r = c
fmt.Println(c, r)
}
Copy the code
A quick explanation of this code: Two interfaces are defined: coder and runner. An entity type Gopher is defined, which implements two methods, run() and code(). The main function defines an interface variable c, binds to a Gopher object, and assigns C to another interface variable R. The assignment succeeds because c contains the run() method. Thus, the two interface variables complete the transformation.
Execute command:
go tool compile -S ./src/main.go
Copy the code
Get the assembly command for the main function, you can see: Call runtime.convI2I(SB); convI2I(SB); convI2I(SB);
func convI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
Copy the code
The code is relatively simple. The function parameters inter represent the interface type, I represent the interface bound to the entity type, and r represent the new iFace after the interface transformation. Iface consists of two fields: TAB and data. So, in fact, all convI2I really needs to do is find the TAB and data for the new interface, and that’s it.
We also know that TAB is made up of interfacetype interfacetype and entity type _type. So the most critical statement is r.tabb = getitab(inter, tab._type, false).
Therefore, focus on the source of the getitab function, just look at the key points:
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
/ /...
// Compute the hash value based on inter and TYp
h := itabhash(inter, typ)
// look twice - once without lock, once with.
// common case will be no lock contention.
var m *itab
var locked int
for locked = 0; locked < 2; locked++ {
if locked ! = 0 {
lock(&ifaceLock)
}
// Iterate over a slot in the hash table
for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m ! = nil; m = m.link {
// If itab has been found in the hash table (both inter and TYp Pointers are the same)
if m.inter == inter && m._type == typ {
/ /...
if locked ! = 0 {
unlock(&ifaceLock)
}
return m
}
}
}
// If no ITab is found in the hash table, create a new ITab
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
// Add to the global hash table
additab(m, true, canfail)
unlock(&ifaceLock)
if m.bad {
return nil
}
return m
}
Copy the code
To summarize, the getitab function looks for the global ITAB hash table based on the interfaceType and _type, and returns the global ITAB hash table if found. Otherwise, a new ITAB is generated based on the given interfaceType and _type, and inserted into the ITAB hash table, so that the next time the ITAB can be retrieved directly.
I did it twice, and I locked it the second time, because if I didn’t find it the first time, if I still didn’t find itAB the second time, I need to generate a new one and write to the hash table, so I need to lock it. This way, if other coroutines look for the same ITAB and don’t find it, the second lookup will be hung, and after that, the itAB that the first coroutine wrote to the hash table will be found.
Look at the code for the additab function again:
// Check that the _type matches the interface_type and create the itab structure to put it in the hash table
func additab(m *itab, locked, canfail bool) {
inter := m.inter
typ := m._type
x := typ.uncommon()
// both inter and typ have method sorted by name,
// and interface names are unique,
// so can iterate over both in lock step;
// the loop is O(ni+nt) not O(ni*nt).
//
// The inter and TYP methods are sorted by method name
// And method names are unique. So the number of cycles is fixed
// Just loop O(ni+nt) instead of O(ni*nt)
ni := len(inter.mhdr)
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
itype := inter.typ.typeOff(i.ityp)
name := inter.typ.nameOff(i.name)
iname := name.name()
ipkg := name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ {
t := &xmhdr[j]
tname := typ.nameOff(t.name)
// Check whether the method name is consistent
if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
if tname.isExported() || pkgPath == ipkg {
if m ! = nil {
// Get the function address and add it to the itab.fun array
ifn := typ.textOff(t.ifn)
*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
}
goto nextimethod
}
}
}
/ /...
m.bad = true
break
nextimethod:
}
if ! locked {
throw("invalid itab locking")
}
// Computes the hash value
h := itabhash(inter, typ)
// Add to the Hash Slot list
m.link = hash[h]
m.inhash = true
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}
Copy the code
Additab checks whether the interfaceType held by itAB matches the _type, that is, whether the _type fully implements the interfaceType method. So if you look at the method lists of the two the overlap is the method list that interfaceType holds. Noticed that one of the double loop, at first glance, cycles is ni * nt, but as a result of the function list is sorted by the function name, so only perform the ni + nt times, the code by a tip: the second loop is not starting from zero, but from the last position of the traverse to the beginning.
Finding the hash function is simpler:
func itabhash(inter *interfacetype, typ *_type) uint32 {
h := inter.typ.hash
h += 17 * typ.hash
return h % hashSize
}
Copy the code
The value of hashSize is 1009.
More generally, conV family functions are called when an entity type is assigned to an interface, such as convT2E to a null interface and convT2I to a non-null interface. These functions are similar:
1. When the interface type is empty, the _type field directly copies the _type of the source type. Call mallocGC to get a new piece of memory, copy the values into it, and data points to the new memory.
2. When the specific type is converted to a non-empty interface, the input parameter TAB is pre-generated by the compiler during compilation. The TAB field of the new interface directly points to the ITAB that TAB points to. Call mallocGC to get a new piece of memory, copy the values into it, and data points to the new memory.
3. For the interface forwarding interface, ITAB calls getitab to obtain it. It is generated only once and then retrieved directly from the hash table.
9. How to use interface to implement polymorphism
The Go language does not design concepts such as virtual functions, pure virtual functions, inheritance, multiple inheritance, etc., but it supports object-oriented features elegantly through interfaces.
Polymorphism is a run-time behavior that has the following characteristics:
1. One type has multiple types of capabilities
2. Allow different objects to react flexibly to the same message
3. Treat all objects in a generic way
4. Non-dynamic languages must be implemented through inheritance and interfaces
Look at an example of code that implements polymorphism:
package main
import "fmt"
func main() {
qcrao := Student{age: 18}
whatJob(&qcrao)
growUp(&qcrao)
fmt.Println(qcrao)
stefno := Programmer{age: 100}
whatJob(stefno)
growUp(stefno)
fmt.Println(stefno)
}
func whatJob(p Person) {
p.job()
}
func growUp(p Person) {
p.growUp()
}
type Person interface {
job()
growUp()
}
type Student struct {
age int
}
func (p Student) job() {
fmt.Println("I am a student.")
return
}
func (p *Student) growUp() {
p.age += 1
return
}
type Programmer struct {
age int
}
func (p Programmer) job() {
fmt.Println("I am a programmer.")
return
}
func (p Programmer) growUp() {
// Programmers grow old too fast
p.age += 10
return
}
Copy the code
This code defines a Person interface that contains two functions:
job()
growUp()
Copy the code
We then define two more constructs, Student and Programmer, while the types *Student and Programmer implement the two functions defined by the Person interface. Note that the *Student type implements the interface, but the Student type does not.
After that, I’ve defined two functions whose parameters are the Person interface:
func whatJob(p Person)
func growUp(p Person)
Copy the code
The main function becomes the Student and Programmer objects, which are passed to the whatJob and growUp functions, respectively. Function, the interface function is called directly, and the actual execution is based on what type of entity is passed in, calling the function implemented by the entity type. Thus, different objects can have multiple representations of the same message, and polymorphism is realized.
To dig a little deeper, inside the function whatJob() or growUp(), the interface person binds the entity type *Student or Programmer. Fun [0] : s.tabb ->fun[0] : s.tabb ->fun[0] : s.tabb ->fun[0] : s.tabb ->fun[0] : s.tabb ->fun[0] : s.tabb ->fun[0]
Run the code:
I am a student.
{19}
I am a programmer.
{100}
Copy the code
10. What are the similarities and differences between Go interface and C++ interface
An interface defines a specification that describes the behavior and functionality of a class without implementing it.
C++ interfaces are implemented using abstract classes. A class is abstract if at least one of its functions is declared pure virtual. Pure virtual functions are specified by using “= 0” in the declaration. Such as:
class Shape
{
public:
// A pure virtual function
virtual double getArea() = 0;
private:
string name; / / name
};
Copy the code
Abstract classes are designed to provide an appropriate base class from which other classes can inherit. An abstract class cannot be used to instantiate an object; it can only be used as an interface.
A derived class needs to explicitly declare that it inherits from the base class, and it needs to implement all pure virtual functions in the base class.
The way C++ defines an interface is called “intrusive,” while Go takes the “non-intrusive” approach. It does not require an explicit declaration, but simply implements the functions defined by the interface, which the compiler automatically recognizes.
The differences in the way C++ and Go define interfaces also lead to differences in the underlying implementation. C++ uses virtual function tables to enable base classes to call functions of derived classes. Go uses the FUN field in ITAB to implement interface variables that call entity-type functions. Virtual function tables in C++ are generated at compile time; The FUN field in Go’s ITAB is dynamically generated at run time. The reason for this is that entity types in Go may inadvertently implement N interfaces, many of which are not necessarily needed, and therefore cannot generate an ITAB for all interfaces implemented by the type. This is also the effect of “non-invasive”. This does not exist in C++ because derivation needs to be explicitly declared from which base class it inherits.
The resources
https://zhuanlan.zhihu.com/p/27055513 contains reflection, such as interface source code analysis 】 【
The difference between the virtual function table and c + + 】 mp.weixin.qq.com/s/jU9HeR1tO…
The concrete types to meet KouFu value 】 https://tiancaiamao.gitbooks.io/go-internals/content/zh/07.2.html
“Go night reading group discussion” https://github.com/developer-learning/reading-go/blob/master/content/discuss/2018-08-30-understanding-go-inter faces.md
【 Liao Xuefeng The duck type] https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431865288798deef438d865e4c29 85acff7e9fad15e3000
The value type and pointer types, iface source 】 https://www.jianshu.com/p/5f8ecbe4f6af
Overall description itab generation, function 】 【 http://www.codeceo.com/article/go-interface.html
Series of conv function role 】 【 https://blog.csdn.net/zhonglinzhang/article/details/85772336
【 convI2I itab role 】 https://www.jianshu.com/p/a5e99b1d50b1
【 interface source code interpretation is very good Contains reflection] http://wudaijun.com/2018/01/go-interface-implement/
【 what according to how train of thought to write interface] http://legendtkl.com/2017/06/12/understanding-golang-interface/
[there] assembly analysis, good at http://legendtkl.com/2017/07/01/golang-interface-implement/
The first picture you can refer to GDB debugging 】 【 https://www.do1618.com/archives/797/golang-interface%E5%88%86%E6%9E%90/
The type conversion and assertions 】 https://my.oschina.net/goal/blog/194308
Interface and nil 】 【 https://my.oschina.net/goal/blog/194233
【 functions and methods 】 https://www.jianshu.com/p/5376e15966b3
“Reflection” https://flycode.co/archives/267357
[interface features list] https://segmentfault.com/a/1190000011451232
【 interface comprehensive introduction, contains a c + + contrast 】 https://www.jianshu.com/p/b38b1719636e
【 Go medallion 42 interface] https://github.com/ffhelicopter/Go42/blob/master/content/42_19_interface.md
There are said to Go interface of the definition of the interface] http://blog.zhaojie.me/2013/04/why-i-dont-like-go-style-interface-or-structural-typing.html
“Gopher interface” http://fuxiaohei.me/2017/4/22/gopherchina-2017.html
The translation is good 】 【 mp.weixin.qq.com/s/tBg8D1qXH…
【 】 infoQ article https://www.infoq.cn/article/go-interface-talk
[Go interface explanation] https://zhuanlan.zhihu.com/p/27055513
[Go interface] https://sanyuesha.com/2017/07/22/how-to-understand-go-interface/
【 getitab source description 】 https://www.twblogs.net/a/5c245d59bd9eee16b3db561d
【 golang IO package use 】 https://www.jianshu.com/p/8c33f7c84509
【 explore c + + as the underlying implementation of interface with the Go to https://www.jianshu.com/p/073c09a05da7 https://github.com/teh-cmc/go-internals/blob/master/chapter2_interfaces/README.md
[Compilation level] http://xargin.com/go-and-interface/
https://i6448038.github.io/2018/10/01/Golang-interface/ has a figure 】 【
Figure 】 【 mp.weixin.qq.com/s/px9BRQrTC…
[English open source book] https://github.com/cch123/go-internals/blob/master/chapter2_interfaces/README.md
http://xargin.com/go-and-interface/