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.