接口类型断言补充

空接口断言

示例:

type User struct {
	name string
}

func main() {
	var a interface{}
	a = User{name: "hello"}
	val, ok := a.(int)
	if ok {
		fmt.Println("is int", val)
	} else {
		fmt.Println("not int")
	}
}

打印汇编信息:

go tool compile -S -N -l main.go > main.s
"".main STEXT size=563 args=0x0 locals=0xf8
	0x0000 00000 (main.go:9)	TEXT	"".main(SB), ABIInternal, $248-0
	0x0000 00000 (main.go:9)	MOVQ	TLS, CX
	0x0009 00009 (main.go:9)	PCDATA	$0, $-2
	0x0009 00009 (main.go:9)	MOVQ	(CX)(TLS*2), CX
	0x0010 00016 (main.go:9)	PCDATA	$0, $-1
	0x0010 00016 (main.go:9)	LEAQ	-120(SP), AX
	0x0015 00021 (main.go:9)	CMPQ	AX, 16(CX)
	0x0019 00025 (main.go:9)	PCDATA	$0, $-2
	0x0019 00025 (main.go:9)	JLS	553
	0x001f 00031 (main.go:9)	PCDATA	$0, $-1
	0x001f 00031 (main.go:9)	SUBQ	$248, SP
	0x0026 00038 (main.go:9)	MOVQ	BP, 240(SP)
	0x002e 00046 (main.go:9)	LEAQ	240(SP), BP
	0x0036 00054 (main.go:9)	PCDATA	$0, $-2
	0x0036 00054 (main.go:9)	PCDATA	$1, $-2
	0x0036 00054 (main.go:9)	FUNCDATA	$0, gclocals·fcf5af2016adf65a97b579a67730f1b6(SB)
	0x0036 00054 (main.go:9)	FUNCDATA	$1, gclocals·4ade6565211d30a1bc0a23317c5a6629(SB)
	0x0036 00054 (main.go:9)	FUNCDATA	$2, gclocals·f08c0631293fa71ce239c95c8d919476(SB)
	0x0036 00054 (main.go:9)	FUNCDATA	$3, "".main.stkobj(SB)
	0x0036 00054 (main.go:10)	PCDATA	$0, $0
	0x0036 00054 (main.go:10)	PCDATA	$1, $0
	0x0036 00054 (main.go:10)	XORPS	X0, X0
	0x0039 00057 (main.go:10)	MOVUPS	X0, "".a+96(SP)
	0x003e 00062 (main.go:11)	XORPS	X0, X0
	0x0041 00065 (main.go:11)	MOVUPS	X0, ""..autotmp_3+144(SP)
	0x0049 00073 (main.go:11)	PCDATA	$0, $1
	0x0049 00073 (main.go:11)	LEAQ	go.string."hello"(SB), AX
	0x0050 00080 (main.go:11)	MOVQ	AX, ""..autotmp_3+144(SP)
	0x0058 00088 (main.go:11)	MOVQ	$5, ""..autotmp_3+152(SP)
	0x0064 00100 (main.go:11)	PCDATA	$0, $0
	0x0064 00100 (main.go:11)	PCDATA	$1, $1
	0x0064 00100 (main.go:11)	MOVQ	AX, ""..autotmp_8+112(SP)
	0x0069 00105 (main.go:11)	MOVQ	$5, ""..autotmp_8+120(SP)
	0x0072 00114 (main.go:11)	PCDATA	$0, $1
	0x0072 00114 (main.go:11)	PCDATA	$1, $2
	0x0072 00114 (main.go:11)	LEAQ	type."".User(SB), AX	//取User的类型信息	
	0x0079 00121 (main.go:11)	PCDATA	$0, $0
	0x0079 00121 (main.go:11)	MOVQ	AX, "".a+96(SP)			//存储User的类型信息
	0x007e 00126 (main.go:11)	PCDATA	$0, $1
	0x007e 00126 (main.go:11)	PCDATA	$1, $3
	0x007e 00126 (main.go:11)	LEAQ	""..autotmp_8+112(SP), AX
	0x0083 00131 (main.go:11)	PCDATA	$0, $0
	0x0083 00131 (main.go:11)	MOVQ	AX, "".a+104(SP)		//存储数据的地址

上面汇编代码的作用是在栈上组装一个eface,赋值给a,因为eface是由_type和ptr组成。

"".a+96(SP)"".a+104(SP)
0x0088 00136 (main.go:12)   MOVQ   "".a+96(SP), AX		//eface的_type
0x008d 00141 (main.go:12)  PCDATA $0, $2
0x008d 00141 (main.go:12)  PCDATA $1, $0
0x008d 00141 (main.go:12)  MOVQ   "".a+104(SP), CX
0x0092 00146 (main.go:12)  PCDATA $0, $3
0x0092 00146 (main.go:12)  LEAQ   type.int(SB), DX		//断言的类型
0x0099 00153 (main.go:12)  PCDATA $0, $2
0x0099 00153 (main.go:12)  CMPQ   AX, DX
0x009c 00156 (main.go:12)  JEQ    163
0x009e 00158 (main.go:12)  JMP    544

类型断言实际上就是用eface的_type与断言的类型进行比较。

非空接口的类型断言

示例:

package main

import "fmt"

type InterUser interface {
	GetName() string
}

type User struct {
	name string
}

func (u User) GetName() string {
	return u.name
}

func main() {
	var u = User{
		name: "hello",
	}
	i := InterUser(u)		// 将u转换成InterUser类型

	switch i.(type) {
	case User:				
		fmt.Println("is User")
	case InterUser:
		fmt.Println("is InterUser")
	}
}

打印汇编信息:

"".main STEXT size=542 args=0x0 locals=0xd0
   ...
   0x0039 00057 (main.go:18)  MOVUPS X0, "".u+72(SP)
   0x003e 00062 (main.go:19)  PCDATA $0, $1
   0x003e 00062 (main.go:19)  LEAQ   go.string."hello"(SB), AX
   0x0045 00069 (main.go:19)  MOVQ   AX, "".u+72(SP)
   0x004a 00074 (main.go:19)  MOVQ   $5, "".u+80(SP)		//分配变量u
   0x0053 00083 (main.go:21)  PCDATA $0, $0
   0x0053 00083 (main.go:21)  PCDATA $1, $1
   0x0053 00083 (main.go:21)  MOVQ   AX, ""..autotmp_3+120(SP)
   0x0058 00088 (main.go:21)  MOVQ   $5, ""..autotmp_3+128(SP)
   0x0064 00100 (main.go:21)  PCDATA $0, $1
   0x0064 00100 (main.go:21)  LEAQ   go.itab."".User,"".InterUser(SB), AX	//获取itab信息
   0x006b 00107 (main.go:21)  MOVQ   AX, "".i+88(SP)					//存储itab信息
   0x0070 00112 (main.go:21)  PCDATA $0, $2
   0x0070 00112 (main.go:21)  PCDATA $1, $0
   0x0070 00112 (main.go:21)  LEAQ   ""..autotmp_3+120(SP), CX
   0x0075 00117 (main.go:21)  MOVQ   CX, "".i+96(SP)			//将变量u的地址赋值给i的ptr
   0x007a 00122 (main.go:23)  PCDATA $1, $2
   0x007a 00122 (main.go:23)  MOVQ   AX, ""..autotmp_4+104(SP)//将变量的itab赋给autotmp_4+104(SP)
   0x007f 00127 (main.go:23)  PCDATA $0, $1
   0x007f 00127 (main.go:23)  MOVQ   CX, ""..autotmp_4+112(SP)
   0x0084 00132 (main.go:23)  JMP    134
   0x0086 00134 (main.go:23)  PCDATA $0, $0
   0x0086 00134 (main.go:23)  TESTB  AL, (AX)
   0x0088 00136 (main.go:23)  MOVL   go.itab."".User,"".InterUser+16(SB), AX
   0x008e 00142 (main.go:23)  MOVL   AX, ""..autotmp_6+52(SP)
   0x0092 00146 (main.go:23)  CMPL   AX, $2002392420
   0x0097 00151 (main.go:23)  JEQ    158
   0x0099 00153 (main.go:23)  JMP    527
   0x009e 00158 (main.go:23)  PCDATA $0, $1
   0x009e 00158 (main.go:23)  LEAQ   go.itab."".User,"".InterUser(SB), AX // 获取User的itab信息
   0x00a5 00165 (main.go:23)  PCDATA $0, $0
   0x00a5 00165 (main.go:23)  CMPQ   ""..autotmp_4+104(SP), AX 	//判断i是不是User类型
   ...
   0x0171 00369 (main.go:23)  CALL   runtime.assertI2I2(SB)	//判断i是不是InterUser类型
   ...

通过汇编可以看出,非空类型的接口进行断言的时候,如果断言的类型是一个具体的类型比如User,将会比较itab字段,如果断言是一个接口类型比如InterUser会调用runtime.assertI2I2