“还在死记硬背类型断言的“规则”?不如一起画一画~”
01
—
var e interface{}//......r,ok := e.(*os.File)

f,_ := os.Open("eggo.txt")e = f

f := "eggo"e = f

02
—
var rw io.ReadWriter//......r,ok := rw.(*os.File)
rw.(*os.File)是要判断rw的动态类型是否为*os.File。前面我们介绍过,程序中用到的itab结构体都会缓存起来,可以通过<接口类型, 动态类型>组合起来的key,查找到对应的itab指针。
所以这里的类型断言只需要一次比较就能完成,就是看iface.tab是否等于<io.ReadWriter, *os.File>这个组合对应的itab指针就好。

f,_ := os.Open("eggo.txt)rw = f

type eggo struct {name string}func (e *eggo) Read(b []byte) (n int, err error) {return len(e.name), nil}func (e *eggo) Write(b []byte) (n int, err error) {return len(e.name), nil}
f := eggo{name: "eggo"}rw = &f

03
—
var e interface{}//......rw,ok := e.(io.ReadWriter)

f,_ := os.Open("eggo.txt")e = f
e的动态类型就是*os.File,我们知道*os.File类型元数据的后面可以找到该类型实现的方法列表描述信息。

图:e赋值为*os.File类型
回到例子中,这里会断言成功,ok为true,rw就是一个io.ReadWriter类型的变量,其动态值与e相同。tab指向<io.ReadWriter, *os.File>对应的itab结构体。

图:e.(io.ReadWriter)类型断言成功
f := "eggo"e = f
然而如果把一个字符串赋值给e,它的动态类型就是string,<io.ReadWriter, string>这个组合会对应下面这个itab,它也会被缓存起来。

图:断言失败的itab
断言失败,ok为false,rw为io.ReadWriter的类型零值,即tab和data均为nil。

图:e.(io.ReadWriter)类型断言失败
04
—
var w io.Writer//......rw,ok := w.(io.ReadWriter)

f,_ := os.Open("eggo.txt")w = f

要确定*os.File是否实现了io.ReadWriter接口,同样会先去itab缓存里查找<io.ReadWriter,*os.File>对应的itab,若存在,且itab.fun[0]不等于0,则断言成功;若不存在,再去检查*os.File的方法列表,创建并缓存itab信息。
这里断言成功,ok为true,rw为io.ReadWriter类型的变量,动态值与w相同,而itab是<io.ReadWriter, *os.File>对应的那一个。

图:w.(io.ReadWriter)断言成功
下面我们自定义一个eggo类型,且*eggo类型只实现io.Writer要求的Write方法,并没有实现io.ReadWriter额外要求的Read方法。如果把一个*eggo类型的变量赋给w:
type eggo struct {name string}func (e *eggo) Write(b []byte) (n int, err error) {return len(e.name), nil}f := eggo{name: "eggo"}w = &f
此时,w的动态类型为*eggo,而*eggo的方法列表里缺少一个Read方法,所以类型断言失败,下面这个itab被缓存起来。

断言失败后,ok为false,rw的data和tab均为nil。

图:w.(io.ReadWriter)类型断言成功
综上,类型断言的关键是明确接口的动态类型,以及对应的类型实现了哪些方法。而明确这些的关键,还是类型元数据,以及空接口与非空接口的数据结构。接下来的Type Switch也不外如是。
05
—
var e interface{}str := "eggo"e = strswitch b := e.(type) {case *os.File:{fmt.Println("*os.File")}case string:{fmt.Println(b) //选择这个分支}default:fmt.Println("default")}

有时会遇到多个类型放在一个分支的情况,这时b的类型是interface{}。
switch b := e.(type) {case *os.File:{fmt.Println("这里b的类型为*os.File", b)}case string:{fmt.Println("这里b的类型为string", b)}case int, int32, int64:{fmt.Println("多类型分支里b的类型为interface{}", b)}default:fmt.Println("default")}





