反射
推荐学习:【Golang】图解反射
前言
用到反射的场景不多,一般是变量类型不确定,内部结构不明朗的情况,反正我现在一次都没有用过,但是不妨碍我们来看看底层到底是怎么回事。
reflect 有两个核心类型:reflect.Type 和 reflect.Value。他们撑起了反射功能的基本框架。
Type
reflect.Type 是一个接口类型,定义了一系列方法获取类型各方面的信息。
type Type interface {
Align() int //对齐边界
FieldAlign() int //作为结构体字段的对齐边界
Method(int) Method //获取方法数组中第i个Method
MethodByName(string) (Method, bool) //按照名称查找方法
NumMethod() int //方法列表中可导出方法的数目
Name() string //类型名称
PkgPath() string //包路径
Size() uintptr //该类型变量占用字节数
String() string //获取类型的字符串表示
Kind() Kind //类型对应的reflect.Kind
Implements(u Type) bool //该类型是否实现了接口u
AssignableTo(u Type) bool //是否可以赋值给类型u
ConvertibleTo(u Type) bool //是否可转换为类型u
Comparable() bool //是否可比较
//只能应用于某些Kind的方法
//Int*, Uint*, Float*, Complex*:
Bits() int
//Array,Ptr,Slice,Map:
Elem() Type
//Array
Len() int
//Chan:ChanDir, Elem
ChanDir() ChanDir
//Func:
In(i int) Type
NumIn() int
Out(i int) Type
NumOut() int
IsVariadic() bool
//Map:
Key() Type
//Struct:
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
common() *rtype
uncommon() *uncommonType
}
我们通常会用 reflect.TypeOf 这个函数去拿到 reflect.Type 类型的返回值、
// TypeOf 返回表示 i 的动态类型的反射类型。如果 i 是一个 nil 接口值,则 TypeOf 返回 nil。
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
举个例子🌰:
a:="eggo"
t:=reflect.TypeOf(a)
fmt.Println(t.Name())
我们要反射 int 类型的变量 a。
我们依旧从函数调用栈开始分析,main 函数栈帧中有两个局部变量 t 与 a,然后是返回值空间,最后是参数。我们知道 Go 语言中参数都是值拷贝。难道这里参数是拷贝变量 a 的值吗?不行,TypeOf 函数的参数类型是空接口。
type eface struct {
_type *_type //指向接口的动态类型元数据
data unsafe.Pointer //指向接口的动态值。
}
_type 字段接受的是 a 的动态类型,data 接受的是 a 的地址。
拷贝不了值,那拷贝变量 a 的地址?也不行,因为这样并不符合Go 语言中传参值拷贝的语义,无论对参数做怎么样的修改,都不能作用到局部变量。
**既然拷贝不了值,也不能拷贝值,那应该怎么办?**实际上在编译阶段会增加一个临时变量作为 a 的拷贝值(copy of a),再把这个临时变量的地址传递给函数使用,这样无论函数对参数做怎么样的修改,都不会作用到局部变量 a。既满足了空接口类型的参数也符合传参值拷贝的语义。
当参数是空接口情况,都是通过传递拷贝后变量的地址来实现传值的语义。
接下来,reflect.TypeOf 函数会把 runtime.eface 类型的参数 i 转化成 reflect.emptInterface 类型并赋给变量 eface,转换以后方便 reflect 包操作内部元素。
// emptyInterface 是 interface{} 值的标头。
type emptyInterface struct {
typ *rtype //string
word unsafe.Pointer //->i.data
}
因为 *rtype 实现了 reflect.Type 接口,TypeOf 函数要做的就是把 eface 的 typ 字段取出来,包装成 reflect.Type 类型的返回值就可以了,那么这就是 toType 函数干的活
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
//上面的代码可以等价于
var ret reflect.Type
ret = eface.typ
reflect.Type 既然是一个非空接口,那么他的结构如下:
type iface struct {
tab *itab
data unsafe.Pointer //接口的动态值 ->eface.typ
}
//itab
type itab struct {
inter *interfacetype //接口的类型元数据 ->reflect.Type
_type *_type //指向接口的动态类型元数据 ->*rtype
hash uint32 //itab._type中拷贝来的,类型哈希值
_ [4]byte
fun [1]uintptr //动态类型实现的接口要求方法地址
}
那么最后返回值的结构就与 iface 结构差不多,tab 指向 <reflect.Type,*rtype> 对应的 itab 指针,data 则是变量 a 的地址。
接下来通过非空接口类型 t 去调用各种方法都会去 string 类型查找相关数据。
Value
reflect.Value 是一个结构体类型。
type Value struct {
typ *rtype //存储反射变量的类型元数据指针
ptr unsafe.Pointer //存储数据地址
flag //是一个位标识符,存储反射变量值的一些描述信息,例如类型掩码,是否为指针,是否为方法,是否只读等等
}
我们来看看反射是如何更新变量值的!
举个例子🌰:
a := "eggo"
v := reflect.ValueOf(a)
v.SetString("new eggo")
println(a)
//panic: reflect: reflect.Value.SetString using unaddressable value
我们来看看 reflect.ValueOf 函数是怎么样的!
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
func escapes(x interface{}) {
if dummy.b {
dummy.x = x
}
}
他的参数是空接口类型,所以接受的参数数据是变量 a 的拷贝值地址。
type eface struct {
_type *_type //指向接口的动态类型元数据 ->stringtype
data unsafe.Pointer //指向接口的动态值。 ->&(copy of a)
}
根据代码注释,escapes 函数会把参数 i 指向得变量(变量 a 得拷贝值 copy of a)逃逸到堆上,那么栈上只会留下他(copy of a)的地址。
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
简单看这个函数逻辑。ValueOf 函数的返回值是 reflect.Value 类型,返回值结构如下:
type Value struct {
typ *rtype //存储反射变量的类型元数据指针 ->就是参数的 _type
ptr unsafe.Pointer //存储数据地址 ->&(copy of a) 根据 unpackEface 来看它来自于 emptyInterface.word 再往前看 其实来自于 参数的 data 字段
flag //是一个位标识符,存储反射变量值的一些描述信息,例如类型掩码,是否为指针,是否为方法,是否只读等等
}
这个返回值会被赋值给局部变量 v,调用 SetString 。
func (v Value) SetString(x string) {
v.mustBeAssignable()
v.mustBe(String)
*(*string)(v.ptr) = x
}
SetString 函数会用到 Value 类型的 ptr 字段。但是经过返回值赋值局部变量 v 他的字段 ptr 好像指向的是 &(copy of a),这是临时变量的地址,不能修改的。所以就会报错。
因为修改这样一个连用户都不知道的临时变量是没有任何意义,同时我们的本意是要修改局部变量 a,所以就会发生 panic。
panic: reflect: reflect.Value.SetString using unaddressable value
好像这样子去修改不符合要求啊!
我们回忆一下,为什么会发生 panic?我们从后面一步一步理一下,失败原因。
- v 的字段 ptr 指向是
&(copy of a) - 为什么会有这个
&(copy of a)?因为传参传的是局部变量a,为了满足语义。
我们现在明白了,要想去修改变量 a,需要反射 a 的指针,只有这样 ptr 才会指向 &a。
举个例子🌰:
func main() {
a := "eggo"
v := reflect.ValueOf(&a)
v.Elem().SetString("new eggo")
println(a)
}
这里传参依旧是值拷贝,这次拷贝 a 的地址,escapes 会将参数指向的变量 a 逃逸到堆上,那么栈上只存他的地址 &a。
剩余的部分与上面那个例子类似,就不过多的描述了,我们来看看不同的地方。
通过调用 v.Elem() 方法然后,再调用的的 SetString 方法就可以修改原来的变量 a了。
为什么一定要先调用 Elem 拿到 ptr 再去修改值呢?
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag.ro()
}
return x
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
func (v Value) SetString(x string) {
v.mustBeAssignable()
v.mustBe(String)
*(*string)(v.ptr) = x
}