Go interface: why is nil interface not equal to nil

Posted by Xzone5 on Fri, 04 Mar 2022 20:45:29 +0100

Static and dynamic characteristics of interface

The static characteristic of the interface is that the interface type variable has a static type. For example, the static type of the variable err in var err error is error. Having static types means that the compiler will type check the assignment operation of all interface type variables at the compilation stage, and the compiler will check whether the type of right value implements all methods in the interface method collection. If not, an error will be reported:

var err error = 1 // cannot use 1 (type int) as type error in assignment: int does not implement error (missing Error method)

The dynamic characteristics of the interface are reflected in that the interface type variable also stores the real type information of the right value at runtime. This right value information is called the dynamic type of the interface type variable.

var err error
err = errors.New("error1")
fmt.Printf("%T\n", err)  // *errors.errorString

nil error value= nil

Let's start with a piece of code:

type MyError struct {
    error
}

var ErrBad = MyError{
    error: errors.New("bad things happened"),
}

func bad() bool {
    return false
}

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = &ErrBad
    }
    return p
}

func main() {
    err := returnsError()
    if err != nil {
        fmt.Printf("error occur: %+v\n", err)
        return
    }
    fmt.Println("ok")
}

The operation results are as follows:

error occur: <nil>

According to our expectation, the program should eventually output ok, but it actually enters the error handling branch.
To understand this problem, we need to further understand the internal representation of interface type variables.

Representation of interface type variables

We can go to $goroot / SRC / Runtime / runtime2 The runtime representation of the interface type variable found in go:

// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
  • eface is used for empty interface type variables without methods, that is, interface {} type variables;
  • iface is used to represent other interface type variables that have methods.
    You can see that they all have two pointer type variables in common.

Look at the internal structure of two different pointers:
_type

// $GOROOT/src/runtime/type.go

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

itab

// $GOROOT/src/runtime/runtime2.go
type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

You can see that in fact, there is one in itab_ The pointer of type just encapsulates several other types of variables. That's what we should pay attention to_ Type, because we only need to judge whether the two interface variables are equal_ Whether the type/tab is the same, and whether the data value stored in the memory space pointed to by the data pointer is the same. Note here that the value of the data pointer is not the same.

First: nil interface variable

func printNilInterface() {
  // nil interface variable
  var i interface{} // Empty interface type
  var err error     // Non empty interface type
  println(i)
  println(err)
  println("i = nil:", i == nil)
  println("err = nil:", err == nil)
  println("i = err:", i == err)
}
//Output:
	(0x0,0x0)
	(0x0,0x0)
	i = nil: true
	err = nil: true
	i = err: true

Whether it is an empty or non empty interface type, if the value of simple life without initialization is nil, its internal type information and data value information are empty.

The second type: empty interface type variable

func printEmptyInterface() {
    var eif1 interface{} // Empty interface type
    var eif2 interface{} // Empty interface type
    var n, m int = 17, 18

    eif1 = n
    eif2 = m

    println("eif1:", eif1)
    println("eif2:", eif2)
    println("eif1 = eif2:", eif1 == eif2) // false

    eif2 = 17
    println("eif1:", eif1)
    println("eif2:", eif2)
    println("eif1 = eif2:", eif1 == eif2) // true

    eif2 = int64(17)
    println("eif1:", eif1)
    println("eif2:", eif2)
    println("eif1 = eif2:", eif1 == eif2) // false
}
//Output:
	eif1: (0x10ac580,0xc00007ef48)
	eif2: (0x10ac580,0xc00007ef40)
	eif1 = eif2: false
	eif1: (0x10ac580,0xc00007ef48)
	eif2: (0x10ac580,0x10eb3d0)
	eif1 = eif2: true
	eif1: (0x10ac580,0xc00007ef48)
	eif2: (0x10ac640,0x10eb3d8)
	eif1 = eif2: false

For empty interface type variables, only_ An equal sign can only be drawn between two empty interface type variables when the data contents referred to by type and data are consistent. In addition, when creating eface, Go will generally reallocate new memory space for data, copy the value of dynamic type variable to this memory space, and point the data pointer to this memory space. Therefore, the data pointer values we see in most cases are different.

The third type: non empty interface type

type T int

func (t T) Error() string { 
    return "bad error"
}

func printNonEmptyInterface() { 
    var err1 error // Non empty interface type
    var err2 error // Non empty interface type
    err1 = (*T)(nil)
    println("err1:", err1)
    println("err1 = nil:", err1 == nil)

    err1 = T(5)
    err2 = T(6)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)

    err2 = fmt.Errorf("%d\n", 5)
    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)
}   
//Output:
	err1: (0x10ed120,0x0)
	err1 = nil: false
	err1: (0x10ed1a0,0x10eb310)
	err2: (0x10ed1a0,0x10eb318)
	err1 = err2: false
	err1: (0x10ed1a0,0x10eb310)
	err2: (0x10ed0c0,0xc000010050)
	err1 = err2: false

For the case of err1 = (*T)(nil), the err1 output from println is (0x10ed120,0x0), that is, the type information of non null interface type variable is not null, and the data pointer is null, so it cannot be equated with nil (0x0,0x0).

The fourth is the equivalence comparison between empty interface type variables and non empty interface type variables

func printEmptyInterfaceAndNonEmptyInterface() {
  var eif interface{} = T(5)
  var err error = T(5)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)

  err = T(6)
  println("eif:", eif)
  println("err:", err)
  println("eif = err:", eif == err)
}
//Output:
	eif: (0x10b3b00,0x10eb4d0)
	err: (0x10ed380,0x10eb4d8)
	eif = err: true
	eif: (0x10b3b00,0x10eb4d0)
	err: (0x10ed380,0x10eb4e0)
	eif = err: false

It can be seen that the internal representation structures of empty interface type variables and non empty interface type variables are different (the first field: _typevs. tab). It seems that they must not be equal.

summary

For Go, eface is used for type comparison_ Type and iface tab_ Type, so as we can see in this example, when eif and err are both assigned T(5), there is an equal sign between them.

Topics: Go Back-end