In this article, I’ll take a look at the interface from the internal degree assignment + assembly perspective, to understand how the interface works.
This article will focus on type conversions and related error-prone areas.
eface
1
2
3
4
5
6
|
func main() {
var ti interface{}
var a int = 100
ti = a
fmt.Println(ti)
}
|
This most common code now raises some questions.
- How to see if ti is eface or iface ?
- Where is the value 100 stored?
- How can I see the real value type of ti?
Most of the source code analysis is done from the assembly, so here is the corresponding assembly posted as well.
1
2
3
4
5
6
7
|
0x0040 00064 (main.go:44) MOVQ $100, (SP)
0x0048 00072 (main.go:44) CALL runtime.convT64(SB)
0x004d 00077 (main.go:44) MOVQ 8(SP), AX
0x0052 00082 (main.go:44) MOVQ AX, ""..autotmp_3+64(SP)
0x0057 00087 (main.go:44) LEAQ type.int(SB), CX
0x005e 00094 (main.go:44) MOVQ CX, "".ti+72(SP)
0x0063 00099 (main.go:44) MOVQ AX, "".ti+80(SP)
|
This assembly has the following features.
- CALL runtime.convT64(SB): takes 100 as the argument to runtime.convT64, which requests a section of memory into which 100 is placed
- put the type type.int into SP+72
- puts the pointer to the memory containing 100 into SP + 80
This assembly is intuitively invisible from the conversion of interface to eface. How can this be observed? This requires the use of gdb.
To dig deeper, how can we use the memory distribution to verify that it is eface? We need to add some additional 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
|
type eface struct {
_type *_type
data unsafe.Pointer
}
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
func main() {
var ti interface{}
var a int = 100
ti = a
fmt.Println("type:", *(*eface)(unsafe.Pointer(&ti))._type)
fmt.Println("data:", *(*int)((*eface)(unsafe.Pointer(&ti)).data))
fmt.Println((*eface)(unsafe.Pointer(&ti)))
}
|
output:
1
2
3
|
type: {8 0 4149441018 15 8 8 2 0x10032e0 0x10e6b60 959 27232}
data: 100
&{0x10ade20 0x1155bc0}
|
From this result, we can see that
- eface.kind = 2, which corresponds to runtime.kindInt
- eface.data = 100
From the memory allocation, we can basically see the memory layout of eface and the corresponding final eface type conversion result.
iface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package main
type Person interface {
Say() string
}
type Man struct {
}
func (m *Man) Say() string {
return "Man"
}
func main() {
var p Person
m := &Man{}
p = m
println(p.Say())
}
|
iface We also look at the assembly.
1
2
3
4
5
6
7
|
0x0029 00041 (main.go:24) LEAQ runtime.zerobase(SB), AX
0x0030 00048 (main.go:24) MOVQ AX, ""..autotmp_6+48(SP)
0x0035 00053 (main.go:24) MOVQ AX, "".m+32(SP)
0x003a 00058 (main.go:25) MOVQ AX, ""..autotmp_3+64(SP)
0x003f 00063 (main.go:25) LEAQ go.itab.*"".Man,"".Person(SB), CX
0x0046 00070 (main.go:25) MOVQ CX, "".p+72(SP)
0x004b 00075 (main.go:25) MOVQ AX, "".p+80(SP)
|
In this assembly, we can see that there is an itab, but the assembly still does not reflect whether it is actually converted to iface.
Again, we continue to use gdb to see that the Person interface is indeed converted to iface.
About iface memory layout, we still add some code to see it.
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
34
35
|
type itab struct {
inter *interfacetype
_type *_type
hash uint32
_ [4]byte
fun [1]uintptr
}
type iface struct {
tab *itab
data unsafe.Pointer
}
type Person interface {
Say() string
}
type Man struct {
Name string
}
func (m *Man) Say() string {
return "Man"
}
func main() {
var p Person
m := &Man{Name: "hhf"}
p = m
println(p.Say())
fmt.Println("itab:", *(*iface)(unsafe.Pointer(&p)).tab)
fmt.Println("data:", *(*Man)((*iface)(unsafe.Pointer(&p)).data))
}
|
output:
1
2
3
|
Man
itab: {0x10b3ba0 0x10b1900 1224794265 [0 0 0 0] [17445152]}
data: {hhf}
|
For those who want to continue exploring the memory layout of eface, iface, you can use unsafe’s related functions to look at the values on the corresponding memory locations based on the above code.
Type assertion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
type Person interface {
Say() string
}
type Man struct {
Name string
}
func (m *Man) Say() string {
return "Man"
}
func main() {
var p Person
m := &Man{Name: "hhf"}
p = m
if m1, ok := p.(*Man); ok {
fmt.Println(m1.Name)
}
}
|
We will focus only on the type assertion piece, posting the corresponding assembly.
1
2
3
4
|
0x0087 00135 (main.go:23) MOVQ "".p+104(SP), AX
0x008c 00140 (main.go:23) MOVQ "".p+112(SP), CX
0x0091 00145 (main.go:23) LEAQ go.itab.*"".Man,"".Person(SB), DX
0x0098 00152 (main.go:23) CMPQ DX, AX
|
Person(SB)` to compare whether they are equal to determine whether the real type of Person is Man.
Another way to assert the type is switch, which is essentially the same thing.
Traps
The most famous trap of interface is the following one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func main() {
var a interface{} = nil
var b *int = nil
isNil(a)
isNil(b)
}
func isNil(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
|
output:
1
2
|
empty interface
non-empty interface
|
Why is this the case? It comes down to the way interface == nil is determined. In general, interface == nil is true only if the type and data of eface are both nil.
When we copy b to interface, x._type.Kind = kindPtr. x.data = nil, but the interface == nil condition is not met.
Suggestions for reading the interface source code
A little advice on reading the interface source code, if you want to use assembly to read the source code, try to choose go1.14.x.
If you choose Go assembly to look at the interface, you basically want to see whether the interface is eventually converted to eface or iface, which functions of runtime are called, and the corresponding function stack distribution. If you choose too high a Go version, the Go assembly will change too much and you may not see the corresponding content in the assembly.