golang reflect实现原理
本文主要讲述reflect库实现的原理思路,reflect包实现具有两个基础unsafe操作内存对齐和runtime包的变量。
runtime变量
runtime变量是reflect的实现基础,基于unsafe包操作runtime变量实现reflect功能。
首先我们按照go的规则先简单的定义一个变量类型Value,Value有两个类型成员属性typ和ptr,typ是类型表示这个变量是什么对象,ptr是一个地址指向这个变量的地址。
// 如果看reflect或runtime源码会发现两者相识,只不过被我删了不少属性。
type Value struct {
typ Type
ptr uintptr
}
type Type interface {
Name() string // by all type
Index(int) Value // by Slice Array
MapIndex(value) Value // by Map
Send(Value) // By Chan
}
当我们去操作一个变量时就按照Type类型来操作,而操作对象的数据就在内存的ptr位置。
变量类型Type定义的是一个接口,因为不同类型有不同的操作方法,例如Map的获取/设置值,Slice和Array的获取一个索引,Chan具有发送和接实一个对象,Struct可以获得一个结构体属性,属性具有tag,这样不同的类型就具有不同独特的操作方法,如果Map类型调用Index方法无法实现就会panic了。
理解变量本质就是一个数据地址和一个类型数据组成,然后基于者两个变量来操作就是reflect。
reflect example
一个reflect简单的例子,reflect.TypeOf
和reflect.ValueOf
方法将一个runtime类型和变量转换成reflect类型和变量,依赖unsafe操作内存对齐来强制转换,reflect类型和变量和runtime中一样的,就可以实现自由操作了。
最后reflect.Value
调用Interface()
方法将变量从reflect状态转换回来成runtime状态了。
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
s := new(Student)
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(s).Elem())
fmt.Println(reflect.TypeOf(*s))
v := reflect.ValueOf(s).Elem()
v.Field(0).SetString("66")
fmt.Printf("%#v\n", v.Interface())
}
reflect Type
先从reflect/type.go简单的抄一点代码来。rtype对象就是Type接口的简化实现,kind就是这个类型的类型,然后其他组合类型(Ptr、Slice、Map等)就额外添加了一些属性和方法。
type rtype struct {
size uintptr
ptrdata uintptr
kind uint8
...
}
ptrType是指针类型的定义,属性rtype就是指针的类型,elem就是指针指向的类型,那么一个Ptr Type调用Elem获得指针的类型就返回了elem值。
// ptrType represents a pointer type.
type ptrType struct {
rtype
elem *rtype // pointer element (pointed at) type
}
structType是指针类型的定义,rtype是结构体类型的基础信息,pkgPath就是结构体的名称,当一个结构体调用Name方法时就返回了pkgPath,如果是结构体指针调用Name方法就没有返回数据,因为没有pkgPath需要先Elem一次转换成结构体类型,而结构体类型的Field、FieldByIndex、FieldByName、FieldByNameFunc方法就对象结构体类型fields信息进行变量操作了。
而在结构体属性structField中,name、typ分别记录这个属性的名称和类型,offsetEmbed是属性偏移位置。
// structType represents a struct type.
type structType struct {
rtype
pkgPath name
fields []structField // sorted by offset
}
// Struct field
type structField struct {
name name // name is always non-empty
typ *rtype // type of field
offsetEmbed uintptr // byte offset of field<<1 | isEmbedded
}
chanType是chan类型的ing有,rtype是chan本身,elem是chan操作对象的类型和指针指向相识,dir就是chan的反向进、出、进出。
// chanType represents a channel type.
type chanType struct {
rtype
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
sliceType是切片类型定义,切片类型rtype是本身信息,elem就是切片操作的对象类型。
// sliceType represents a slice type.
type sliceType struct {
rtype
elem *rtype // slice element type
}
arrayType是数组类型,在切片上额外多了两个属性,slice是数组转换成切片的类型,预先静态定义好了,而len是数组长度。
// arrayType represents a fixed array type.
type arrayType struct {
rtype
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
上述example讲述了部分类型的定义,完整查看源码reflect.type.go。
method、interface、map暂未完全看完,懂原理后没必要看没有几行使用相关知识。
reflect Kind
reflect.Kind是定义反射类型常量,是类型的标识。rtype的kind属性就是指reflect.Kind。
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
reflect Type method
Kind方法注释说明返回kind值就是rtype.kind,类型是reflect.Kind是go中类型的主要分类,是iota定义的类型常量。
// Kind returns the specific kind of this type.
Kind() Kind
变量实现的方法定义在类型连续后面的一块内存中,可以unsafe读到一个类型的全部方法,就可以实现Implements方法判断是否实现了一个接口了。
// Implements reports whether the type implements the interface type u.
Implements(u Type) bool
ChanDir方法很简单就返回chanType.dir,注释说如果不是Chan类型panic了,类型不chan就没有dir这个属性无法处理就panic了,在调用前一般都明确了Kind是Chan。
// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir
Elem方法全称是element,就是指元素类型也可以叫指向类型,注释要求Kind必须是Array、Chan、Map、Ptr、Slice类型否在就panic,和Chan的ChanDir方法一样,只有这5个类型才有elem属性。
查看前面定义就可以知道Arry、Slice、Ptr、Chan的elem就是指向的对象的类型,map是值的类型,例如以下类型Elem后Kind都是Int。
[20]int
[]int
*int
chan int
map[string]int
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
Field和NumField方法是获得结构体的指定索引的属性和结构体属性数量,注释一样有说明要求Kind是Struct类型否在panic,因为就结构体类型才有[]StructField能实现这些方法。
根据前面structType定义两个方法的实现思路就是typ.fields[i]转换一下和len(typ.fields).
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
NumIn和In方法是Func Kind独有的方法,NumIn返回这个Func具有多个入参,对于返回参数就是NumOut;In方法是获得这个Func指定第i参数的类型。
// NumIn returns a function type's input parameter count.
// It panics if the type's Kind is not Func.
NumIn() int
// In returns the type of a function type's i'th input parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
In(i int) Type
Key方法是Map Kind独有方法,返回map键的类型。
// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
Key() Type
Len方法是Array Kind独有方法,返回Array定义的长度。
// Len returns an array type's length.
// It panics if the type's Kind is not Array.
Len() int
上述说明reflect.Type的部分方法实现原理,剩余方法原理类似,就是操作rtype的属性,部分Kind类型是具有独有方法可以调用。
reflect.Value Method
反射Value对象定义了三个属性 类型、数据位置、flag,数据内存位置就在ptr位置,操作方法就需要依靠typ类型来判断数据类型操作了。
Type是静态数据,而Value是动态数据,Value的很多方法具体值是和数据相关的。
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
通用方法
通用方法是指所有类型具有的方法,仅说明根据Type和Value定义实现这个方法大概的思路,具体实现代码并不一样,以源码为准。
Type方法返回这个值的类型,大致思路就是返回v.typ,具体实现还有一些额外处理。
func (v Value) Type() Type
Kind方法实现大致思路就是返回v.typ.kind。
// Kind returns v's Kind. If v is the zero Value (IsValid returns false),
// Kind returns Invalid.
func (v Value) Kind() Kind
Interface 法思路就是返回v.ptr值转换成一个interface{}变量,这样就从reflect.Value重新转换会变量了。
// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing unexported struct fields.
func (v Value) Interface() (i interface{})
Convert 方法思路就是v.ptr值转换成参数t的类型,实现规则是Conversions语法文档 镜像地址。
// Convert returns the value v converted to type t. If the usual Go conversion rules do not allow conversion of the value v to type t, Convert panics.
func (v Value) Convert(t Type) Value
Set 方法实现就是设置v.ptr=x.ptr,要求v和x的类型是一样的。
同时要这个Value是CanSet,如果将一个int转换成reflect.Value,函数传递的是一个值的副本,那么再对int设置新的值就无效了,CanSet返回就是false,需要传递*int这样的指针类型才能有效设置
// Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value)
SetBool 方法是设置bool Kind的值,前置要求Kind是一样的,类型还有SetInt、SetString等方法。
// SetBool sets v's underlying value. It panics if v's Kind is not Bool or if CanSet() is false.
func (v Value) SetBool(x bool)
Method 返回这个值的指定索引方法。
// Method returns a function value corresponding to v's i'th method.
// The arguments to a Call on the returned function should not include
// a receiver; the returned function will always use v as the receiver.
// Method panics if i is out of range or if v is a nil interface value.
func (v Value) Method(i int) Value
独有方法
Len方法返回数据数据,注释说明要求是Array, Chan, Map, Slice, or String,前四个返回就是数据量,而String Kind返回字符串长度。
// It panics if v's Kind is not Array, Chan, Map, Slice, or String.
func (v Value) Len() int
IsNil方法判断指针是否是空,在go的实现中chan、func、interface、map、pointer、slice底层才是指针类型,才能判断IsNil否在panic,判断这些指针类型的ptr是否为0,在go代码编写中也只有这几种类型可以i==nil
这样的比较。
在go1.13中新增了IsZero方法,判断是否是空值,里面这些指针类型会判断IsNil,其他类型就是判断数据值是不是零值那样。
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero Value.
func (v Value) IsNil() bool
Index方法获取指定类型的索引,就Array、Slice、String可以执行,否在panic,在ptr指向的位置进行一个计算得到的偏移位置获得到索引的值。
// Index returns v's i'th element. It panics if v's Kind is not Array, Slice, or String or i is out of range.
func (v Value) Index(i int) Value
Field方法是返回结构体指定索引的值,要求Kind是Struct,通过指定索引的偏移来获得这个值的地址,然后类型里面获得到类型,最后返回索引的值。
// Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range.
func (v Value) Field(i int) Value
Elem方法是返回Ptr和Interface Kind指向值,为了解除引用。
为什么Value.Elem方法没有了Slice、Map等类型? 具体额外独立的操作方法Index、MapIndex等。
// Elem returns the value that the interface v contains or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
func (v Value) Elem() Value
MapIndex和MapKeys是Map Kind独有的方法,获取到map的索引值和全部键,通过typ提供的类型和ptr地址进行复杂的map操作。
// MapIndex returns the value associated with key in the map v.
// It panics if v's Kind is not Map.
// It returns the zero Value if key is not found in the map or if v represents a nil map.
// As in Go, the key's value must be assignable to the map's key type.
func (v Value) MapIndex(key Value) Value
// MapKeys returns a slice containing all the keys present in the map,
// in unspecified order.
// It panics if v's Kind is not Map.
// It returns an empty slice if v represents a nil map.
func (v Value) MapKeys() []Value
Send方法是Chan Kind独有方法,给chan放一个数据进去。
func (v Value) Send(x Value)
end
以上讲述了reflect库的原理就是操作runtime变量,而runtime变量就是一个类型加地址。
本文并没有完整分析reflect库,通过这些原理就可以大概理解这些方法的作用和操作了,具体请参考源码。
有疑问加站长微信联系(非本文作者)