Ten problems about interface of deep decryption Go language (I)
The relationship between Go language and duck type
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 as:
- If something looks like a duck, swims like a duck, quacks like a duck, it can be regarded as a duck
-
Duck Typing, duck type, is an object inference strategy for dynamic programming languages
- It focuses more on how objects can be used
- Not the type of the object itself
-
Go language as a static language
- It perfectly supports duck types through interfaces
For example, in the dynamic language python, define a function like:
def hello_world(coder): coder.say_hello()
-
When this function is called
- Any type can be passed in
- As long as it implements say_ Just use the hello () function
-
If not implemented, errors will occur during operation
-
In static languages such as Java and C + +
- You must explicitly declare that an interface is implemented
- After that, it can be used wherever this interface is needed
- If you call hello_ in the program World function
- But it introduces a saying that does not implement say at all_ Type of hello()
- Then it won't pass at the compilation stage
-
This is why static languages are safer than dynamic languages
-
The difference between dynamic language and static language is reflected here
-
Static languages can find type mismatch errors during compilation
- Unlike dynamic languages, you have to run to that line of code to report an error
-
of course
- Static language requires programmers to write programs according to regulations in the coding stage
- Specify the data type for each variable
- This is to some extent
- Increased workload
- It also increases the amount of code
- Dynamic languages do not have these requirements
- It can make people focus more on business
- The code is also shorter
- It's faster to write
- It can make people focus more on business
- Static language requires programmers to write programs according to regulations in the coding stage
-
Students who write python know this better
-
Go language as a modern static language
- It has the advantage of late development
-
It introduces the convenience of dynamic language
- At the same time, static language type checking will be carried out
-
It's very Happy to write
-
Go adopted a compromise approach:
- Declare that an interface is implemented without requiring type display
- As long as the relevant methods are implemented
- The compiler can detect
-
Let's take an example:
package main import "fmt" // First define an interface and functions that use this interface as parameters: type IGreeting interface { sayHello() } func sayHello(i IGreeting) { i.sayHello() } // Then 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!") } func main() { golang := Go{} php := PHP{} sayHello(golang) sayHello(php) } ``` type IGreeting interface { sayHello() } func sayHello(i IGreeting) { i.sayHello() } 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
- Passed in golang, php object
- They do not explicitly declare or implement the IGreeting type
- Only the sayHello() function specified by the interface is implemented
- actually
- When the compiler calls the sayHello() function
- It implicitly converts golang and PHP objects to IGreeting type
- This is also the type checking function of static language
By the way, let's mention the characteristics of dynamic language:
The type of variable binding is uncertain
It can only be determined during operation
Functions and methods can receive any type of parameter
And the parameter type is not checked when calling
No interface is required
- To sum up
- Duck type is a dynamic language style
- In this style
- Effective semantics of an object
- Not by inheriting from a specific class or implementing a specific interface
- It is determined by its "collection of current methods and properties"
- In this style
- Go as a static language
- It is realized through the interface Duck type
- In fact, it is the Go compiler that does the covert conversion work
Difference between value receiver and pointer receiver
method
-
Method can add new behavior to user-defined types
-
It differs from a function in that a method has a receiver
- Add a receiver to a function
- Then it becomes a method
-
The receiver can be a value receiver or a pointer receiver.
-
When calling a method
- Value type
- You can call the method of the value receiver
- You can also call the method of the pointer receiver
- Pointer type
- You can call the method of the pointer receiver
- You can also call the method of the value receiver
- Value type
-
in other words
- No matter what type of receiver the method is
- Both values and pointers of this type can be called
- It is not necessary to strictly conform to the type of receiver
Let's take 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()) // The value type calls a method whose receiver is a pointer type qcrao.growUp() fmt.Println(qcrao.howOld()) // ---------------------- // A value type calls a method whose receiver is also a value type fmt.Println(qcrao.howOld()) // The value type calls a method whose receiver is a pointer type qcrao.growUp() fmt.Println(qcrao.howOld()) // ---------------------- } // The output result of the above example is: 18 19 100 101
-
After calling the growUp function
- Whether the caller is a value type or a pointer type
- Its Age values have changed
-
actually
- When the receiver types of type and method are different
- In fact, the compiler did some work behind it
- Present in a table:
- | Value recipient | Pointer receiver |
---|---|---|
Value type caller | Method uses a copy of the caller, similar to "pass value" | Use the reference of the value to call the method. In the above example, qcrao Growth () is actually (&qcrao) growUp() |
Pointer type caller | The pointer is dereferenced as a value. In the above example, stefno Howold() is actually (* stefno) howOld() | In fact, it is also "value transfer". The operation in the method will affect the caller. It is similar to pointer parameter transfer, which copies a pointer |
Value receiver and pointer receiver
-
As mentioned earlier
- Whether the receiver type is a value type or a pointer type
- Can be called by value type or pointer type
- It actually works through grammar sugar
- Whether the receiver type is a value type or a pointer type
-
Let's start with the conclusion:
- Implementing a method in which the receiver is a value type is equivalent to automatically implementing a method in which the receiver is a pointer type
- The method that implements the receiver is a pointer type will not automatically generate the method that the corresponding receiver is a value type
Take a look at an example and you will fully understand:
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() } // The above code defines an interface coder, which defines two functions: code() debug() // Then a structure Gopher is defined, which implements two methods, a value receiver and a pointer receiver. // Finally, we call the two defined functions through the variables of interface type in the main function // Run it and the result is: I am coding Go language I am debuging Go language // But if we change the first statement of the main function: func main() { var c coder = Gopher{"Go"} c.code() c.debug() } // Run 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)
-
See the difference between the two codes?
- The first time is to assign & gopher to coder
- The second time is to assign Gopher to coder.
-
The second error is
- Gopher does not implement coder
-
It's obvious
- Because the Gopher type does not implement the debug method
- On the surface, * Gopher type does not implement code method
- But because the Gopher type implements the code method
- So let * Gopher type have code method automatically
-
Of course, the above statement has a simple explanation:
- The receiver is a pointer type method
- It is likely that the recipient's properties will be changed in the method
- This affects the recipient
- For the receiver, it is a method of value type
- There is no impact on the receiver itself in the method
- The receiver is a pointer type method
-
Therefore, when implementing a method whose receiver is a value type
- You can automatically generate a method whose receiver is the corresponding pointer type
- Because neither will affect the recipient
-
However, when implementing a method whose receiver is a pointer type
- If a method whose receiver is a value type is automatically generated at this time
- Originally expected change to receiver (via pointer)
- Not now
- Because the value type will produce a copy
- Does not really affect the caller
Finally, just remember the following:
If you implement a method whose receiver is a value type, you implicitly implement a method whose receiver is a pointer type
When are they used
-
If the recipient of the method is a value type
- Whether the caller is an object or an object pointer
- What you modify is a copy of the object
- Does not affect the caller
- Whether the caller is an object or an object pointer
-
If the receiver of the method is a pointer type
- The caller modifies the object itself pointed to by the pointer
-
Reasons for using pointers as recipients of methods:
- Method can modify the value pointed to by the receiver
- Avoid copying this value every time a method is called
- When the type of value is a large structure
- This will be more efficient
-
Use value receiver or pointer receiver
- It is not determined by whether the method modifies the caller (that is, the receiver)
- It should be based on the nature of the type
-
If the type has "primitive essence"
-
In other words, its members are primitive types built in the Go language
- Such as string, integer value, etc
- Then define the method of the value receiver type
- Such as string, integer value, etc
-
Like built-in reference types
- Such as slice, map, interface and channel
- These types are special. When you declare them, you actually create a header
- For them, it is also a method to directly define the value receiver type
- In this way, when the function is called
- These types of header s are copied directly
- The header itself is designed for replication.
- Such as slice, map, interface and channel
-
If the type has a non primitive nature
- Cannot be copied safely
- This type should always be shared
- Then define the method of the pointer receiver
- For example, the structure file in the go source code should not be copied
- There should be only one entity
- Cannot be copied safely
-
This paragraph is rather convoluted. You can Go to the section 5.3 of Go language practice
What is the difference between iface and eface
- iface and eface are the underlying structures that describe interfaces in Go
- The difference is
- The interface described by iface contains methods
- eface is an empty interface without any methods: interface {}
From the perspective of 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 }
-
iface internally maintains two pointers
- tab points to an itab entity
- It represents the type of interface and the entity type assigned to this interface
- data points to the specific value of the interface
- Generally speaking, it is a pointer to heap memory
- tab points to an itab entity
-
Let's take a closer look at the itab structure:
- _ The type field describes the type of entity
- Including memory alignment, size, etc
- The inter field describes the type of interface
- The fun field places the method address of the specific data type corresponding to the interface method
- Implement the dynamic dispatch of interface calling methods
- Generally, this table will be updated every time a conversion occurs to an interface assignment
- Or take the cached itab directly
- _ The type field describes the type of entity
-
Only entity type and interface related methods will be listed here
-
Other methods of entity types do not appear here
- If you have studied C + +, you can compare the concept of virtual function here
-
In addition, you may wonder why the size of the fun 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, continue to store in the memory space after it
- From an assembly point of view
- These function pointers can be obtained by adding addresses, which has no impact
- Incidentally, these methods are arranged in the dictionary order of function names
Take another look at the interface type, which describes the interface type:
type interfacetype struct { typ _type pkgpath name mhdr []imethod }
- Can see
- It's packed_ Type type
- _ type is actually a structure that describes various data types in Go language
- We note that
- There is also an mhdr field
- Represents the list of functions defined by the interface
- The pkgpath record defines the package name of the interface
Here is a picture of the iface structure:
Next, let's look at the source code of eface:
type eface struct { _type *_type data unsafe.Pointer }
- Compared with iface
- eface is simpler
- Only one is maintained_ type field
- Represents the specific entity type carried by the empty interface
- data describes the specific values
- Only one is maintained_ type field
Let's take an example:
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() { x := 200 var any interface{} = x fmt.Println(any) g := Gopher{"Go"} var c coder = g fmt.Println(c) } // Execute the command and print out the assembly language: go tool compile -S ./src/main.go // You can see that 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 can be associated with the fields of iface and eface structures:
- Both functions are
- Assemble the parameters
- Form the final interface
- Both functions are
-
As a supplement, let's take a final look_ 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 correlation align uint8 fieldalign uint8 // Type number, including bool, slice, struct, etc kind uint8 alg *typeAlg // gc correlation gcdata *byte str nameOff ptrToThis typeOff }
- Various data types of Go language are in_ Based on the type field, some additional fields are added for management:
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 definition of these data types is the basis of reflection implementation
Dynamic type and dynamic value of interface
- From the source code, we can see that iface contains two fields:
- tab is the interface table pointer
- Point to type information
- Data is a data pointer
- Point to specific data
- They are called dynamic types and dynamic values, respectively
- Interface values include dynamic types and dynamic values
- tab is the interface table pointer
[Extension 1] compare the interface type with nil
- The zero value of the interface value means that both the dynamic type and the dynamic value are nil
- If and only if the values of both parts are nil
- This interface value will be considered as interface value = = nil
Let's take 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) } // Output: true c: <nil>, <nil> true false c: *main.Gopher, <nil>
- in limine
- The dynamic type and dynamic value of c are nil
- g is also nil
- When g is assigned to c
- The dynamic type of c becomes * main Gopher
- Only the dynamic value of c is still nil
- But when c is compared with nil
- The result is false
[Extension 2] let's take an example and see its output:
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 } // Function running result: <nil> false
- Here, a MyError structure is defined first
- The Error function is implemented
- The error interface is implemented
- The Process function returned an error interface
- This implies type conversion
- therefore
- Although its value is nil
- In fact, its type is * MyError
- Finally, when compared with nil, the result is false
[extension 3] how to print the dynamic type and value of the interface?
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))) } // The operation results are as follows: {0 0} {17426912 0} {17426912 842350714568} 5
-
An iface structure is directly defined in the code
- Use two pointers to describe itab and data
- Then, the contents of a, B and C in memory are forcibly interpreted as our custom iface
- Finally, you can print out the addresses of dynamic types and dynamic values
-
The address of the dynamic type and dynamic value of a is 0, that is, nil
-
The dynamic type of b is consistent with that of c, both of which are * int
-
The dynamic value of c is 5
The compiler automatically detects whether a type implements an interface
It is often seen that some open source libraries have strange usages like the following:
var _ io.Writer = (*myWriter)(nil)
- I'll be a little confused at this time
- I don't know what the author wants to do
- In fact, this is the answer to this question
- The compiler checks whether the * myWriter type implements io Writer interface
Let's take 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 io Writer interface var _ io.Writer = (*myWriter)(nil) // Check whether the myWriter type implements io Writer interface var _ io.Writer = myWriter{} } // 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)
- Error message: * myWriter/myWriter not implemented io The writer interface, that is, the Write method is not implemented.
- After the comment is released, the program will run without error
- In fact, the above assignment statement will have implicit type conversion
- In the process of conversion
- The compiler checks whether the type to the right of the equal sign implements the function specified by the interface to the left of the equal sign
- In the process of conversion
To sum up, you can add the following code to the code to detect whether the type implements the interface:
var _ io.Writer = (*myWriter)(nil) var _ io.Writer = myWriter{}