Article catalog
- 1. Preface
- 2. Unexpected comparison results
- 3. Find the problem
- 4. Pit avoidance method
- 5. Summary
- reference
1. Preface
Interface is a very important feature provided in Go. One or more functions can be defined in an interface. For example, the definition of io.ReadWriter provided by the system is as follows:
type ReadWriter interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error) }
As long as any type provides the implementation of Read and Write, this type implements the interface (duck type), unlike Java, which requires developers to use implementations.
However, there are many pits in the use of Go interface, which needs special attention. This paper records a problem of comparing nil with interface {}.
2. Unexpected comparison results
First, let's look at a comparison problem of comparing nil slices.
func IsNil(i interface{}) { if i == nil { fmt.Println("i is nil") return } fmt.Println("i isn't nil") } func main() { var sl []string if sl == nil { fmt.Println("sl is nil") } IsNil(sl) }
What is the result of running the above code? You may be as confident as I am that the output is as follows:
sl is nil i is nil
But the actual output is:
sl is nil i isn't nil
Oh my god, why does a nil slice become non nil as soon as it passes through the empty interface {}.
3. Find the problem
To understand this problem, you first need to understand the nature of the interface {} variable.
There are two slightly different interfaces in the Go language. One is an interface with a set of methods, and the other is an empty interface without any methods.
The Go language uses runtime.iface to represent an interface with methods, and runtime.eface to represent an empty interface without any methods.

A variable of interface {} type contains two pointers, one pointer points to the type of value and the other pointer points to the actual value. Under the runtime package in the Go source code, we can find the definition of runtime.eface.
type eface struct { // 16 bytes _type *_type data unsafe.Pointer }
It can be seen from the definition of null interface that when a null interface variable is nil, both pointers need to be 0.
Back to the original problem, we print the value of the null interface variable in the incoming function to see its two pointer values.
// InterfaceStruct defines the internal structure of an interface {} type InterfaceStruct struct { pt uintptr // Pointer to value type pv uintptr // Pointer to value content } // ToInterfaceStruct converts an interface {} to InterfaceStruct func ToInterfaceStruct(i interface{}) InterfaceStruct { return *(*InterfaceStruct)(unsafe.Pointer(&i)) } func IsNil(i interface{}) { fmt.Printf("i value is %+v\n", ToInterfaceStruct(i)) } func main() { var sl []string IsNil(sl) IsNil(nil) }
Run output:
i value is {pt:6769760 pv:824635080536} i value is {pt:0 pv:0}
It can be seen that although sl is a nil slice, it is essentially a variable with the type [] string and the value of the empty structure slice. Therefore, when sl is passed to the empty interface, it is a non nil variable.
On closer examination, you may ask, since sl is a slice with type and value, why is it a nil.
For a specific type of variable, whether it is nil depends on whether its value is zero. Because sl is a slice type, and the definition of slice type can be found in the source package src/runtime/slice.go.
type slice struct { array unsafe.Pointer len int cap int }
Let's continue to see whether the slice corresponding to the slice with the value of nil is zero.
type slice struct { array unsafe.Pointer len int cap int } func main() { var sl []string fmt.Printf("sl value is %+v\n", *(*slice)(unsafe.Pointer(&sl))) }
Run output:
sl value is {array:<nil> len:0 cap:0}
As expected, it was really zero.
So far, the reason behind the unexpected comparison results at the beginning is explained: the empty slice is nil because its value is zero. After the empty slice of type [] string is passed to the empty interface, because the value of the empty interface is not zero, the interface variable is not nil.
4. Pit avoidance method
Know there is a pit in front, how to avoid falling in. There are only two ways we can think of: filling the pit and bypassing it.
Because this is determined by the characteristics of Go, the straight white point is designed like this. In the face of this pit, we developers can only sigh and feel helpless. Leave it to the genius of the Go core team to fill the pit.
All we can do is bypass the pit. There are two ways to bypass it: (1) Since there is such an inexplicable situation when a typed variable with a value of nil is assigned to an empty interface, we should not do so. We should first judge the empty before assigning a value, and assign it to an empty interface when it is not nil; (2) Use reflect.ValueOf().IsNil() to judge. This method is not recommended because when the type corresponding to the empty interface is a value type, it will panic.
func IsNil(i interface{}) { if i != nil { if reflect.ValueOf(i).IsNil() { fmt.Println("i is nil") return } fmt.Println("i isn't nil") } fmt.Println("i is nil") } func main() { var sl []string IsNil(sl) // i is nil IsNil(nil) // i is nil }
5. Summary
Go is simple, efficient and powerful. Such an excellent language also has many misused features. This paper records the problem of failure of judging the empty interface when nil, one of the ten thousand pits, is assigned to the empty interface. Later, we will continue to give common problems about the use of go to help you step on the pit less.
Remember, whether the variable in Go is nil depends on whether the value of the variable is zero.
Remember, do not assign a variable with nil value to an empty interface.
reference
Important points for Golang interface equivalence comparison The first pit of Go language - Comparison between interface and nil Go language design and implementation. 4.2 interface Discussion on the security of Golang Concurrent Assignment