Go进阶之接口类型内部表示

11 阅读7分钟

接口是Go这门静态类型语言中唯一动静兼备的语言特性.

1.静态特性:

1).接口类型变量具有静态类型.比如var e error中变量e的静态类型为error.

2).支持在编译阶段的类型检查.当一个接口类型变量被赋值时.编译器会检查右值是否

实现了该接口集合中的所有方法.

2.动态特性:

1).接口类型变量兼具动态类型.在运行时存储在接口类型变量中的值的真实类型.比如

var i interface{}=13中接口变量i的类型为int.

2).接口类型变量在程序运行时可以被赋值为不同的动态类型变量.从而支持多态.

3.nill error !=nil?

示例:

```
type MyError struct {
    error
}

var ErrBad = MyError{
    errors.New("Bad"),
}

func Bad() bool {
    return false
}

func ReturnError() error {
    var p *MyError = nil
    if Bad() {
       p = &ErrBad
    }
    return p
}
```

main方法:

```
func main() {
    err := Concurrent.ReturnError()
    if err != nil {
       fmt.Printf("error: %s\n", err)
       return
    }
    fmt.Printf("ok")
}
```

执行结果:

image.png

从结果可以得知.并没有按照预想的打印ok.接下来就进入探索之路.

4.接口类型变量内部表示:

接口类型的动静兼备特性决定了变量内部表示不像静态类型那样.

源码位置:src/runtime/runtime2.go

```
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

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

可以看到在运行层面.接口类型变量有两种内部表示--eface和iface.

1).eface:用于表示没有方法的空接口(empty interface)类型变量.即interface{}

类型的变量.

2).iface:用于表示其余拥有方法的接口(interface)类型变量.

这两种结构的共同点是都有两个指针字段.并且第二个指针字段的功用相同.都指向当

前赋值给该接口类型变量的动态类型变量的值.

不通点在于eface所表示的空接口类型并无方法列表.因此第一个指针指向一个_type

类型结构.源码如下:

```
type Type struct {
    Size_       uintptr
    PtrBytes    uintptr // number of (prefix) bytes in the type that can contain pointers
    Hash        uint32  // hash of type; avoids computation in hash tables
    TFlag       TFlag   // extra type information flags
    Align_      uint8   // alignment of variable with this type
    FieldAlign_ uint8   // alignment of struct field with this type
    Kind_       Kind    // enumeration for C
    // 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.
    // Normally, GCData points to a bitmask that describes the
    // ptr/nonptr fields of the type. The bitmask will have at
    // least PtrBytes/ptrSize bits.
    // If the TFlagGCMaskOnDemand bit is set, GCData is instead a
    // **byte and the pointer to the bitmask is one dereference away.
    // The runtime will build the bitmask if needed.
    // (See runtime/type.go:getGCMask.)
    // Note: multiple types may have the same value of GCData,
    // including when TFlagGCMaskOnDemand is set. The types will, of course,
    // have the same pointer layout (but not necessarily the same size).
    GCData    *byte
    Str       NameOff // string form
    PtrToThis TypeOff // type for pointer to this type, may be zero
}
```

iface除了要存储动态类型信息外.还要存储接口本身的信息(接口的类型信息 方法 列

表信息)以及动态类型所实现的方法的信息.因此iface的第一个字段指向第一个itab

类型结构.源码如下:

```
type ITab struct {
    Inter *InterfaceType
    Type  *Type
    Hash  uint32     // copy of Type.Hash. Used for type switches.
    Fun   [1]uintptr // variable sized. fun[0]==0 means Type does not implement Inter.
}
```

上面itab结构中的第一个字段inter指向的interfacetype结构存储这该接口类型自

身的信息.interfacetype源码如下:

```
type InterfaceType struct {
    Type
    PkgPath Name      // import path
    Methods []Imethod // sorted by hash
}
```

Type:结构类型信息.

pkgpath:包路径名.

mhdr:接口方法集合切片.

示例:

```
type T struct {
    n int
    s string
}

func main() {

    var t = T{
       n: 1,
       s: "hello world",
    }

    var ei interface{} = t

    println(ei)

}
```

image.png

```
type T struct {
    n int
    s string
}

func (T) M1() {

}

func (T) M2() {

}

type NonEmptyInterface interface {
    M1()
    M2()
}

func main() {

    var t = T{
       n: 1,
       s: "hello world",
    }

    var ei NonEmptyInterface = t

    println(ei)
}
```

image.png 从上面的图可以看出.每个接口类型变量在运行时的表示都是由两部分组成.这两种接

口类型分别简记为eface(Type,data)和iface(tab,data).虽然第一个字段有所差异.

tab和Type可以统一看作动态类型信息.Go语言中每种类型都有唯一的Type信息.无

论是内置原生类型还是自定义类型.Go运行时都会为程序内的全部类型建立只读的共

享Type信息表.因此拥有相同动态类型的同类接口类型变量的type和tab信息是相同

的.接口类型变量data部分则指向一个动态分配的内存空间.内存空间存储的是赋值给

接口类型变量的动态类型变量的值.未显示初始化的接口类型变量值为nil.即该变量的

type和data都为nil.所以判断两个接口类型变量是否相同.只需要判断type和data

是否相同.data判断的不是指针的值.是指针指向的数据的值.

eface和iface是runtime包中非导出结构定义.不能在包外运用.但是Go语言提供了

println预定义函数.

源码位置:src/runtime/print.go

```
func printeface(e eface) {
    print("(", e._type, ",", e.data, ")")
}

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

5.nil接口变量:

未赋初始值的接口类型变量的值为nil.这类变量即为nil接口变量.

示例如下:

```
// nil接口变量.
func PrintNilInterface() {
    //空接口类型
    var i interface{}
    //非接口空类型
    var err error
    println(i)
    println(err)
    println("i==nil:", i == nil)
    println("err==nil:", err == nil)
    println("i==err:", i == err)
    println(" ")
}
```

main方法:

```
func main() {

    Concurrent.PrintNilInterface()
}
```

输出结果:

image.png

从结果可以看出.无论是空接口类型还是非空接口类型变量一旦变量值为nil.他们的内

部表示均为(0x0,0x0).类型信息和数据信息都为空.因此上面变量i和err等值判断为

true.

6.空接口类型变量:

示例如下:

```
// 非接口类型变量
func PrintNonEmptyInterface() {
    //非接口类型
    var err1 error
    var err2 error
    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)
    println(" ")
}
```

main方法:

func main() {

	Concurrent.PrintNonEmptyInterface()
}

执行结果:

image.png

对于空接口类型变量.只有在type和data(不是数据指针一致)所指数据内容一致的情

况下.两个空接口类型变量之间才能画等号.

Go在创建eface时一般会为data重新分配内存空间.将动态类型变量的值复制到这块

内存空间.并将data指针指向这块内存空间.所以我们大多数情况下看到的data值是

不同的. 从结果可以看出上面问题的答案.虽然值为nil但是他们的类型不同.所以值比较是

false.

7.空接口类型变量与非空接口类型变量等值比较:

示例如下:

```
// 空接口类型变量与非空接口类型变量.
func PrintEmptyInterfaceAndNonEmptyInterface() {
    var emptyInterface interface{} = T{5}
    var err error = T{5}
    println("emptyInterface", emptyInterface)
    println("err", err)
    println("err == emptyInterface:", err == emptyInterface)

    err = T{6}
    println("emptyInterface", emptyInterface)
    println("err", err)
    println("err == emptyInterface:", err == emptyInterface)
}
```

main方法:

func main() {
	
	Concurrent.PrintEmptyInterfaceAndNonEmptyInterface()
}

执行结果:

image.png

空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.

但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.

空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.

但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.


空接口类型变量和非空接口类型变量内部表示的家结构有所不同.似乎一定不能相等.

但Go在进行等值比较的时候.类型比较使用的是eface的type和iface的tab.type.

像溪流绕过山坡.







如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路