This article is a relatively famous pit in Go. I was asked during the interview before, why did I remember to write this?
Because we have really seen this pit online, we will be vigilant when using if err != nil.
The Go language interface{} has a special feature during use. When you compare whether the value of an interface{} type is nil, this is a problem that needs special attention and avoidance.
Let's take a look at a demo:
package main import "fmt" type ErrorImpl struct{} func (e *ErrorImpl) Error() string { return "" } var ei *ErrorImpl var e error func ErrorImplFun() error { return ei } func main() { f := ErrorImplFun() (f == nil) }
Output:
false
Why not true?
To understand this problem, you first need to understand the nature of the interface{} variable. In Go language, a variable of type interface{} contains 2 pointers, one pointer points to the type determined at compile time of the value, and the other pointer points to the actual value.
// InterfaceStructure defines the internal structure of an interface{}type InterfaceStructure struct { pt uintptr // Pointer to value type pv uintptr // Pointer to the value content} // asInterfaceStructure Convert an interface{} to InterfaceStructurefunc asInterfaceStructure(i interface{}) InterfaceStructure { return *(*InterfaceStructure)((&i)) } func main() { var i1, i2 interface{} var v1 int = 23 var v2 int = 23 i1 = v1 i2 = v2 ("sizeof interface{} = %d\n", (i1)) ("i1 %v %+v\n", i1, asInterfaceStructure(i1)) ("i2 %v %+v\n", i2, asInterfaceStructure(i2)) var nilInterface interface{} var str *string ("nil interface = %+v\n", asInterfaceStructure(nilInterface)) ("nil string = %+v\n", asInterfaceStructure(str)) ("nil = %+v\n", asInterfaceStructure(nil)) }
Output:
sizeof interface{} = 16
i1 23 {pt:4812032 pv:825741246928}
i2 23 {pt:4812032 pv:825741246936}
nil interface = {pt:0 pv:0}
nil string = {pt:4802400 pv:0}
nil = {pt:0 pv:0}
When we assign a specific type value to a variable of type interface{}, we assign both type and value to two pointers in interface{} at the same time. If the value of this specific type is nil, the interface{} variable will still store the corresponding type pointer and value pointer.
How to solve it?
Method 1
The returned result is checked by non-nil, and then assigned to the interface{} variable
type ErrorImpl struct{} func (e *ErrorImpl) Error() string { return "" } var ei *ErrorImpl var e error func ErrorImplFun() error { if ei == nil { return nil } return ei } func main() { f := ErrorImplFun() (f == nil) }
Output:
true
Method 2
Returns the specific implementation type instead of interface{}
package main import "fmt" type ErrorImpl struct{} func (e *ErrorImpl) Error() string { return "" } var ei *ErrorImpl var e error func ErrorImplFun() *ErrorImpl { return ei } func main() { f := ErrorImplFun() (f == nil) }
Output:
true
Solve the pitfalls caused by third-party packages
Since some errors are returned by third-party packages, they do not want to modify the third-party packages, they have to find a way to receive and process them.
Method 1
Using interface{} principle
is:=*(*InterfaceStructure)((&i)) if ==0 && ==0 { //is nil do something }
Print out the underlying pointer to the value and the type pointer to the value. If both are 0, it means nil
Method 2
Use assertions to assert the specific type and then judge non-empty
type ErrorImpl struct{} func (e ErrorImpl) Error() string { return "demo" } var ei *ErrorImpl var e error func ErrorImplFun() error { //ei = &ErrorImpl{} return ei } func main() { f := ErrorImplFun() //Of course, if there are many types of error implementations, use //The switch case method is clearer res, ok := f.(*ErrorImpl) ("ok:%v,f:%v,res:%v", ok, f == nil, res == nil) }
Output:
ok:true,f:false,res:true
Method 3
Utilize reflection
type ErrorImpl struct{} func (e ErrorImpl) Error() string { return "demo" } var ei *ErrorImpl var e error func ErrorImplFun() error { //ei = &ErrorImpl{} return ei } func main() { f := ErrorImplFun() rv := (f) ("%v", ()) }
Output:
true
Note⚠:
Assertion and reflection performance are not particularly good. If you have to use it again, controlling use will help improve program performance.
Panic due to function reception type:
type ErrorImpl struct{} func (e ErrorImpl) Error() string { return "demo" } var ei *ErrorImpl var e error func ErrorImplFun() error { return ei } func main() { f := ErrorImplFun() (()) }
Output:
panic: value method called using nil *ErrorImpl pointer
solve:
func (e *ErrorImpl) Error() string { return "demo" }
Output:
demo
You can find that just turn the receive type into a pointer type.
The above are nil-related pitfalls. I hope everyone can remember that if you encounter "lucky" you can think of these possibilities.
Supplement: The easy point of error in go language interface{}
If goroutine and channel are the two cornerstones of go language concurrency, then interface is the key to go language type abstraction.
In actual projects, almost all data structures at the bottom are interface types.
Speaking of C++ language, we can immediately think of three nouns: encapsulation, inheritance, and polymorphism. Although the go language does not have a strict object, it can be said that polymorphism is realized through interface. (The characteristics of encapsulation and inheritance are realized by combining structures)
package main type animal interface { Move() } type bird struct{} func (self *bird) Move() { println("bird move") } type beast struct{} func (self *beast) Move() { println("beast move") } func animalMove(v animal) { () } func main() { var a *bird var b *beast animalMove(a) // bird move animalMove(b) // beast move }
The go language supports defining members in method, struct, and struct as interface types, and use struct to give a simple chestnut.
Using the interface feature of the go language, polymorphism can be implemented and generic programming can be performed.
2. Interface principle
If you don’t fully understand the essence of interface and use it directly, you will definitely fall into a deep pit in the end. If you want to use it, you must first understand it. Let’s take a look at the interface source code.
type eface struct { _type *_type data } type _type struct { size uintptr // type size ptrdata uintptr // size of memory prefix holding all pointers hash uint32 // hash of type; avoids computation in hash tables tflag tflag // extra type information flags align uint8 // alignment of variable with this type fieldalign uint8 // alignment of struct field with this type kind uint8 // enumeration for C alg *typeAlg // algorithm table gcdata *byte // garbage collection data str nameOff // string form ptrToThis typeOff // type for pointer to this type, may be zero }
You can see that the interface variable can receive any type of variable because it is essentially an object and records its type and pointers to the data block. (In fact, the source code of the interface also includes function structure and memory distribution. Since it is not the focus of this article, students who are interested can understand it themselves)
3. Interface is a pit of empty
For an empty object, we often use the conditional statement of if v == nil to determine whether it is empty, but when the code is filled with interface types, it is often not the result we want to judge empty (in fact, a smart student who knows or is smart from the above-mentioned interface essence is that the object already knows what I want to say)
package main type animal interface { Move() } type bird struct{} func (self *bird) Move() { println("bird move") } type beast struct{} func (self *beast) Move() { println("beast move") } func animalMove(v animal) { if v == nil { println("nil animal") } () } func main() { var a *bird // nil var b *beast // nil animalMove(a) // bird move animalMove(b) // beast move }
It's still the same as the one I just said. In the go language, var a *bird writing method, a just declares its type, but does not apply for a space. So at this time, a is essentially pointing to a null pointer, but we failed to judge null in the aminalMove function, and the following () call is also successful. The essential reason is that interface is an object. When making a function call, it will implicitly convert the null pointer of bird type into an instance interface animal object. So at this time, v is actually not empty, but its data variable points to empty.
At this time, it seems that the execution is normal, so under what circumstances will the pit trip over us? Just add a piece of code
package main type animal interface { Move() } type bird struct { name string } func (self *bird) Move() { println("bird move %s", ) // panic } type beast struct { name string } func (self *beast) Move() { println("beast move %s", ) // panic } func animalMove(v animal) { if v == nil { println("nil animal") } () } func main() { var a *bird // nil var b *beast // nil animalMove(a) // panic animalMove(b) // panic }
In the code, we add a name variable to the derived class and call it in the function implementation, and panic will occur. At this time, self is actually a nil pointer. So the pit came out here.
Some people think that this kind of error can be avoided by being cautious, because we use positive thinking to substitute the interface, but if reverse programming is easy to cause difficult bugs.
package main type animal interface { Move() } type bird struct { name string } func (self *bird) Move() { println("bird move %s", ) } type beast struct { name string } func (self *beast) Move() { println("beast move %s", ) } func animalMove(v animal) { if v == nil { println("nil animal") } () } func getBirdAnimal(name string) *bird { if name != "" { return &bird{name: name} } return nil } func main() { var a animal var b animal a = getBirdAnimal("big bird") b = getBirdAnimal("") // return interface{data:nil} animalMove(a) // bird move big bird animalMove(b) // panic }
Here we see that the instance type pointer is returned through a function. When nil is returned, since the received variable is interface type, implicit conversion is performed again, which leads to panic (this kind of reverse conversion is difficult to detect).
So how do we deal with the above-mentioned problems? I've sorted out three points here
1. Fully understand the principle of interface and be careful during use.
2. Use generic programming with caution, use interface type to receive variables, and also ensure that the interface returns to interface type, rather than instance type
3. The null is to use reflection typeOf and valueOf to convert it into an instance object before null is judged.