Go 反射与interface拾遗

Go能实现接口的动态性和反射的基础是:编译期为运行时提供了类型信息。interface底层有两种结构,上一节讲了带方法的iface,这一节补充不带方法的eface结构。

interface之eface

Go中的任何对象都可以表示为interface{}。它扮演的角色与C中的void*差不多,区别在于interface{}中包含有类型信息,可以实现反射。

efece数据结构描述gcdata域用于垃圾回收,size描述类型的大小,hash表示数据的hash值,align是对齐,fieldalign是这个数据嵌入结构体时的对齐,kind是枚举值。alg是函数指针的数组,存储了hash/equal/print/copy四个函数操作。uncommentType指向这个类型的方法集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type eface struct {
_type *_type
data unsafe.Pointer
}

type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
_unused uint8
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
gcdata *byte
_string *string
x *uncommontype
ptrto *_type
zero *byte // ptr to the zero value for this type
}

运行时convT2E

T2E的转换 赋值给空interface{},运行时会调用conv类函数convT2E。与convT2I一样,向interface的类型转换var i interface{} = u都存在内存拷贝。看iface.go源码时发现convI2I等接口转换时没有分配内存和拷贝数据,原因可能是Go接口内部的data域,并不开放途径让外部修改,所以接口之间转换可以用同一块内存。

1
2
3
4
5
6
7
8
9
10
11
12
func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {
ep := (*eface)(unsafe.Pointer(&e))
...
if x == nil {
x = newobject(t)
}
typedmemmove(t, x, elem)
ep._type = t
ep.data = x
}
return
}

下面的例子中修改u并不影响i interface的内存数据,因为i在赋值时通过convT2E对u进行了拷贝。这也是反射非指针变量时无法直接改变变量数据的原因,因为反射会先把变量转成interface类型,拿到的是变量的副本。

1
2
3
4
5
6
u := User{1, "Tom"}
var i interface{} = u
u.id = 2
u.name = "Jack"
// u {2, "Jack"}
// i.(User) {1, "Tom"}

nil的理解

未初始化的interface类型,指针,函数,slice,cannel和map都是nil的。对于interface比较特殊,只有eface的type和data都是nil,或者iface的type和data都是nil时,interface{}才是nil。

1
2
3
4
5
6
7
type Duck interface{
Walk()
}
var i interface{} // nil
var d Duck // nil
var v *T // nil
i = v // (*T)(nil) not nil

type assertion

严格来说Go并不支持泛型编程,但通过interface可实现泛型编程,后面reflect浅析中有个通过reflect实现泛型的例子。interface像其它类型转换的时候一般需要断言。下面只给出了eface的例子,当然也可以通过断言来判断某个类型是否实现了某个接口。

1
2
3
4
5
6
7
8
9
10
func do(v interface{}) {
n, ok := v.(int)
if !ok {...}
}

func doswitch(i interface{}) {
switch v := i.(type) {
case int: ...
}
}

对应的go源码在iface.go当中。assertE2T过程判断了eface的type字段是否和目标type相等,相等则还需要拷贝数据。assertI2T也要拷贝数据,不过他比较的是iface.tab._type与目标type是否一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func assertE2T(t *_type, e interface{}, r unsafe.Pointer) {
ep := (*eface)(unsafe.Pointer(&e))
if ep._type == nil {
panic(&TypeAssertionError{"", "", *t._string, ""})
}
if ep._type != t {
panic(&TypeAssertionError{"", *ep._type._string, *t._string, ""})
}
if r != nil {
if isDirectIface(t) {
writebarrierptr((*uintptr)(r), uintptr(ep.data))
} else {
typedmemmove(t, r, ep.data)
}
}
}

reflect浅析

反射机制提供了检查存储在接口变量中的[类型 值]对的机制。根据Laws Of ReflectionGo的反射可以总结三点,即反射可以从interface中获取reflect对象;同时可以通过Interface()方法恢复reflect对象为一个interface;如果要修改反射对象,该对象必须是settable的。

TypeOf与ValueOf实现

获取反射对象的实现,是基于对interface底层数据的操作。首先对象经过了convT2E,然后emptyInterface直接指向了eface的type字段。typeOf返回的Type是接口,在类型_type上实现了很多操作。valueOf返回的就是Value结构体,它包含了数据域和type域信息。

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
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}

func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}

func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, unsafe.Pointer(e.word), f}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Type interface{
...
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
NumField()
Field(i) StructField
FieldByName(name string) (StructField, bool)
...
}

type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}

通过Type获取Struct Field信息

可以通过reflect获取类型实例的结构体信息,比如每个field的名字类型或标签。

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
// A StructField describes a single field in a struct.
type StructField struct {
Name string // Name is the field name.
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
nino := Person{"nino", 27}
t := reflect.TypeOf(nino)
n := t.NumField()
for i := 0; i < n; i++ {
fmt.Println(t.Field(i).Name, t.Field(i).Type, t.Field(i).Tag)
}
}
// Name string json:"name"
// Age int json:"age"

通过Value实现泛型

为了解决method接受不同类型的slice为入参,可以用反射来完成。对于可记长度和可随机访问的类型,可以通过v.Len()v.Index(i)获取他们的第几个元素。

v.Index(i).Interface()将reflect.Value反射回了interface类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func method(in interface{}) (ok bool) {
v := reflect.ValueOf(in)
if v.Kind() == reflect.Slice {
ok = true
} else {
return false
}
num := v.Len()
for i := 0; i < num; i++ {
fmt.Println(v.Index(i).Interface())
}
return ok
}

func main() {
s := []int{1, 3, 5, 7, 9}
b := []float64{1.2, 3.4, 5.6, 7.8}
method(s)
method(b)
}

通过Elem修改reflect对象值

对LawsOfReflect第三点的理解 reflect.ValueOf 如果直接传入x,则v是x的一个副本的reflect对象。修改v的值并不会作用到x上。p是指向x的指针的reflect对象,修改p的值是在修改指针的指向,同样不会作用到x上,因此也是CanNotSet的。只有通过p.Elem()相当于获取了*p的reflect对象,这时才能使用v.SetFloat(7.2)来对原始的数据进行修改。

1
2
3
4
var x float64 = 3.4
v := reflect.ValueOf(x) // can not set
p := reflect.ValueOf(&x) // can not set
e := p.Elem() // can set