Recently a reader asked me a question with the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func main() {
    printNonEmptyInterface1()
}

type T struct {
    name string
}
func (t T) Error() string {
    return "bad error"
}
func printNonEmptyInterface1() {
    var err1 error    // Non-null interface type
    var err1ptr error // Non-null interface type
    var err2 error    // Non-null interface type
    var err2ptr error // Non-null interface type

    err1 = T{"eden"}
    err1ptr = &T{"eden"}

    err2 = T{"eden"}
    err2ptr = &T{"eden"}

    println("err1:", err1)
    println("err2:", err2)
    println("err1 = err2:", err1 == err2)             // true
    println("err1ptr:", err1ptr)
    println("err2ptr:", err2ptr)
    println("err1ptr = err2ptr:", err1ptr == err2ptr) // false
}

His question is: “How is it understood that when dynamic types are pointers, interface variables are not equal, and when dynamic types are not pointers, interface variables are equal?” .

This question reminds me of the famous “nil error ! = nil” problem, which has caused a lot of confusion for Go beginners. Let’s review the example code of this problem in the GO FAQ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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")
}

Running this example, we will get the following results.

1
error occur: <nil>

In the case of “nil error ! = nil”, I will give you a brief explanation of how to determine whether two interface type variables are equal.

Go has been open source for more than 13 years, and there is a lot of information available from various sources. Often people learn a little deeper, they know that Go’s interface types are represented at runtime as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

What the two structures have in common is that they both have two pointer fields, the first of which has a similar function in that it represents type information. And the second pointer field has the same function, both pointing to the value of the dynamic type variable currently assigned to that interface type variable.

In this way, determining whether two interface type variables are equal is a matter of determining whether the type information in this runtime representation is equal to the DATA information. We can use Go’s built-in println function to output the runtime representation of the interface variables. The Go compiler replaces println with specific runtime functions at the compilation stage, depending on the type of the argument to be output, which are defined in the $GOROOT/src/runtime/print.go file, and the print functions for the eface and iface types, the print functions are implemented as follows.

1
2
3
4
5
6
7
8
// $GOROOT/src/runtime/print.go
func printeface(e eface) {
    print("(", e._type, ",", e.data, ")")
}

func printiface(i iface) {
    print("(", i.tab, ",", i.data, ")")
}

We can see from the implementation of printeface and printiface that println will output the type information of the interface type variable with the data information. Let’s take the example in the Go FAQ above, if we use println to output the error type variable returned by returnsError and compare it with error(nil), the code is as follows.

1
2
3
4
5
6
func main() {
    err := returnsError()
    println(err)
    println(error(nil))
    ... ...
}

We will get the following output.

1
2
(0x4b7318,0x0) // println(err)
(0x0,0x0) // println(error(nil))

We see that the type information part of error(nil) is nil, while the type information part of err is not null, so they must not be equal, which is why this example outputs the “unexpected” “error occur: “.

Let’s go back to the example at the beginning of this article, and run the example, the output is as follows.

1
2
3
4
5
6
err1: (0x10c6cc0,0xc000092f20)
err2: (0x10c6cc0,0xc000092f40)
err1 = err2: true
err1ptr: (0x10c6c40,0xc000092f50)
err2ptr: (0x10c6c40,0xc000092f30)
err1ptr = err2ptr: false

We see that the type information part of the interface type variable is the same regardless of whether the dynamic type of the interface variable uses a pointer or a non-pointer, and the data part is different. But why does one output true and the other output false?

To find the real reason, I used the lensm tool to graphically show the correspondence between the assembly and the source Go code.

lensm tool

Note: Versions prior to lensm v0.0.3 do not work for Go 1.20 compiled programs and cannot display the assembly corresponding source.

We see from the diagram that Go calls runtime.ifaceeq to compare, whether err1 == err2 or err1ptr == err2ptr! Let’s look at the comparison logic of ifaceeq.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// $GOROOT/src/runtime/alg.go
func efaceeq(t *_type, x, y unsafe.Pointer) bool {
      if t == nil {
          return true
      }
      eq := t.equal
      if eq == nil {
          panic(errorString("comparing uncomparable type " + t.string()))
      }
      if isDirectIface(t) {
          // Direct interface types are ptr, chan, map, func, and single-element structs/arrays thereof.
          // Maps and funcs are not comparable, so they can't reach here.
          // Ptrs, chans, and single-element items can be compared directly using ==.
          return x == y
      }
      return eq(x, y)
} 

func ifaceeq(tab *itab, x, y unsafe.Pointer) bool {
    if tab == nil {
        return true
    }
    t := tab._type
    eq := t.equal
    if eq == nil {
        panic(errorString("comparing uncomparable type " + t.string()))
    }
    if isDirectIface(t) {
        // See comment in efaceeq.
        return x == y
    }
    return eq(x, y)
}

This time it is clear to determine the equality of interface type variables (as seen by the comment below the isDirectIface function in efaceeq)!

In the case that the type information (_type/tab field) of two interface type variables is the same, for dynamic types that are pointers (a type of direct interface type), the direct comparison is between the type pointers of the two interface type variables; in the case of other non-pointer types (Go will allocate additional memory storage, and data is a pointer to a new memory block), the The eq function in the type (_type) information is called, and the implementation of the eq function is also an “==” equivalence judgment after the data is dereferenced. Of course, like the example in the Go FAQ, if the type information (_type/tab field) of two interface type variables is different, then the two interface type variables are definitely not equal.

Well, the reader’s question at the beginning of the article can now be resolved.

  • err1 and err2 both interface variables have dynamic type T, so the comparison is the value of the memory block pointed to by data. Although the data fields of err1 and err2 point to two memory blocks, the value of the T object in these two memory blocks is the same (essentially a string), so err1 == err2 is true.
  • err1ptr and err2ptr both interface variables are of dynamic type *T, so the comparison is directly to the value of data, which is obviously different, so err1ptr == err2ptr is false.

Ref https://tonybai.com/2023/02/19/how-to-determine-if-two-interface-vars-are-equal/