SoFunction
Updated on 2025-03-04

Analysis of several pits and principles based on go interface{}==nil

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.