reflect

Written by with ♥ on in Go

文档

官方定义:Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.

reflect 实现运行时(run time)反射,允许程序操作任意类型的对象,在运行时访问、检测和修改对象。

go 内部缺少泛型支持,只能通过反射来支持一些动态特性,例如 fmt 等标准库都十分依赖反射机制。

reflect 包抽象出两个最基本的类型表示对象的信息:

  1. Type Interface
  2. Value Struct

Type Interface

先看 Type 的定义:

type Type interface {
	Align() int // 类型对齐后变量在内存中占有的字节数
	FieldAlign() int // 如果是 struct 的字段,对齐后占有的字节数
	Method(int) Method // 返回方法集里的第 i 个方法
	MethodByName(string) (Method, bool) // 通过名称获取方法集中的方法
	NumMethod() int // 方法集中导出方法的个数
	Name() string // 类型名称
	PkgPath() string // 类型所在的路径,例如 encoding/base64
	Size() uintptr // 返回类型的大小,与 unsafe.Sizeof 类似
	String() string // 类型的字符串表示,实现 fmt.Stringer 接口
	Kind() Kind // 返回底层类型
	Implements(u Type) bool // 类型是否实现接口 u
	AssignableTo(u Type) bool // 类型是否可以赋值给另一个类型 u   
	ConvertibleTo(u Type) bool // 类型是否可以转换成类型 u
	Comparable() bool // 类型是否可以比较
	
	// 下面的函数只有特定类型可以调用
	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Ptr: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
        // sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int // 类型所占据的位数
	ChanDir() ChanDir // 返回 channel 的方向
	IsVariadic() bool // 返回函数类型是否有可变参数
	Elem() Type // 返回内部子元素的类型
	Field(i int) StructField // 返回结构体类型的第 i 个字段
   	FieldByIndex(index []int) StructField // 返回嵌套的结构体字段 []int{1, 1}
	FieldByName(name string) (StructField, bool) // 通过字段名获取字段
	FieldByNameFunc(match func(string) bool) (StructField, bool) // 返回名称符号 func 函数的字段
	In(i int) Type // 获取函数类型第 i 个参数的类型
	Key() Type // 返回 map 的 key 类型
	Len() int // 返回 Array 的长度,只能 Array 调用
	NumField() int // 返回结构体的字段数量,只能 Struct 调用
	NumIn() int // 返回函数类型输入参数个数
	NumOut() int // 返回函数类型返回值个数
	Out(i int) Type // 返回函数类型第 i 个参数的类型
   	common() *rtype // 表示公共的类型信息
	uncommon() *uncommonType // 表示独有的类型信息,例如 Array 有自己独特的类型信息,下面会细讲
}

定义中有很多函数签名,反应类型系统的方方面面,在 reflect 包内部的非导出结构体 rtype 实现 Type 接口,其结构与 runtime/type.go 中的 _type 结构体字段保持一致。

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
	size       uintptr
	ptrdata    uintptr  // number of 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       uint8    // enumeration for C
	alg        *typeAlg // algorithm table
	gcdata     *byte    // garbage collection data
	str        nameOff  // string form
	ptrToThis  typeOff  // type for pointer to this type, may be zero
}

TypeOf()

使用 TypeOf 函数生成 Type 实例,可以看到参数是 interface{},空接口里面直接包含类型信息:

func TypeOf(i interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

emptyInterface 对应空接口的数据结构,里面都是已经熟悉的 _type 和 data 指针:

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

eface.typ 就是 rtypetoType 只做了一个类型转换:

func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

使用 rtype 实现 Type 接口,隐藏的语法是 var x Type = t

TypeOf 函数只解析了所有类型公共的 rtype 部分,所有类型都包含 rtype,如果是 ArrayType 等类型会有一些额外的信息在内存中,反射包(reflect/type.go)中有相关的定义,所有的基本类型都能找到:

// arrayType represents a fixed array type.
type arrayType struct {
	rtype
	elem  *rtype // array element type
	slice *rtype // slice type
	len   uintptr
}

type funcType struct {
	rtype
	inCount  uint16
	outCount uint16 // top bit is set if last input parameter is ...
}

// chanType represents a channel type.
type chanType struct {
	rtype
	elem *rtype  // channel element type
	dir  uintptr // channel direction (ChanDir)
}

...

Type 接口实现 String() 函数,满足 fmt.stringer 接口,在使用 fmt.Pringln 打印时,会调用 String 函数输出结果。

Elem()

返回内部元素的类型,如果不是容器类型或指针会报错。

// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.

返回类型对应的元素类型,例如数组等容器中元素的类型,指针指向的元素类型。

Implements()

ConvertibleTo()

AssignableTo()

Value Struct

Value 表示 interface{} 中存储的实际变量,提供实际变量的各种信息,方法通过需要结合类型信息和值信息,所以 Value 也存了一份 rtype 的引用。

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

Value 中包含 rtype 存储对象的类型信息。

ValueOf()

使用 ValueOf 函数生成 Value 实例,参数也是 interface{}

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

	return unpackEface(i)
}

主要功能都在 unpackEface 函数中:

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.
   // 在我们不知道 data 是不是指针之前,不要读取 data
	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) { // kind 必须大于 16,第 5 位才是 1
		f |= flagIndir
	}
	return Value{t, e.word, f}
}

Value 中存储 rtype、word 和标志位 flag。

先看 flag 定义:

type flag uintptr

const (
	flagKindWidth        = 5 // there are 27 kinds // 用 5 个 bit 表示类型,总共有 27 种类型,5 个 bit 能表示 32 种类型
	flagKindMask    flag = 1<<flagKindWidth - 1 // 11111
	flagStickyRO    flag = 1 << 5 // 100000
	flagEmbedRO     flag = 1 << 6 // 1000000
	flagIndir       flag = 1 << 7 // 10000000 // 间接位
	flagAddr        flag = 1 << 8 // 100000000
	flagMethod      flag = 1 << 9 // 1000000000
	flagMethodShift      = 10 // 1010
	flagRO          flag = flagStickyRO | flagEmbedRO // 1100000
)

先计算通过 f := flag(t.Kind()) 计算 f,获取的是底层 Kind 的对应数字:

func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }

计算 rtype.kind & kindMask

kindMask = (1 << 5) - 1 // 11111 取出前 5 位就是类型

t.kind = 54(110110) & kindMask(11111) 运算结果为二进制 `10110`(2 + 4 + 16) 等于十进制 22 等于 Kind 类型中的 Ptr

ifaceIndir 反映类型 t 是否间接存储在接口值中,啥意思?Direct 直接 Indirect 间接,建议阅读 Value Parts

  • 指针是 Direct
  • slice 是 Indirect
  • int 是 Indirect
// ifaceIndir reports whether t is stored indirectly in an interface value.
func ifaceIndir(t *rtype) bool {
	return t.kind&kindDirectIface == 0
}

kindDirectIface = 1 << 5 // 100000

指针的底层数字是 54:110110

100000
&
110110
=
100000 == 0 // false,也就是说类型 t 不间接存储在 data 值中,不执行 f |= flagIndir

字符串的底层 kind 是 24:11000

100000
&
011000
= 
0 == 0 // true,也就是说类型 t 间接存储在 data 值中,执行 f |= flagIndir

如果返回第 6 位是 1,设置 flagIndir 到 flag:

f |= flagIndir

flagIndir 是 flag = 1 << 7 // 10000000

设置 f 的第 7 位为 1,表示当前值是 InDir。

Call()

  • Call: 调用函数,本身需要是函数类型

Type()

unpackEface()

Indirect()

返回 Value 指针指向的真实值,本质上就是返回指向的内存区域。

// Indirect returns the value that v points to.
// 如果 v 是空指针,返回零值 Value
// 如果不是指针类型,直接返回
func Indirect(v Value) Value {
   if v.Kind() != Ptr {
      return v
   }
   return v.Elem()
}

为什么需要 Indirect?

IsValid()

报告 v 是否为零值,此时返回 false。

func (v Value) IsValid() bool {
	return v.flag != 0
}

Elem()

Elem 返回 Value 包含指针指向的内存区域对应的值。

Elem 返回 Value 中包含的值(也就是 eface 中的 data),如果类型不是 Interface 或者指针,会 panic

// 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 {
   k := v.kind()
   switch k {
   case Interface: // 目测很少有情况会进这个 case,现在还不太明确,使用的地方也很少(绝大多数接口都有动态值,kind 都不是 interface)
      var eface interface{} // 初始化 eface
      if v.typ.NumMethod() == 0 {
         eface = *(*interface{})(v.ptr)
      } else {
         eface = (interface{})(*(*interface {
            M()
         })(v.ptr))
      }
      x := unpackEface(eface) // unpackEface converts the empty interface i to a Value.
      if x.flag != 0 {
         x.flag |= v.flag.ro() // ???
      }
      return x
   case Ptr: // 主要关注 Ptr Case
      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 // 取出指向类型的 type
      fl := v.flag&flagRO | flagIndir | flagAddr // 设置标志位 flagRO(1100000) 情况前 5 位 kind,设置第 8 位 flagIndir(10000000) 和第 9 位 flagAddr(100000000) 表示可取址 CanAddr
      fl |= flag(typ.Kind()) // 设置前 5 位 kind
      return Value{typ, ptr, fl} // 返回新的 Value 实例
   }
   panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

测试:

i := User{Name: "test"}
fmt.Println(reflect.ValueOf(&i).Elem()) // {test}
fmt.Println(reflect.ValueOf(&i).Elem().Elem()) // panic: reflect: call of reflect.Value.Elem on struct Value

CanAddr()

// CanAddr reports whether the value's address can be obtained with Addr.
// Such values are called addressable. A value is addressable if it is
// an element of a slice, an element of an addressable array,
// a field of an addressable struct, or the result of dereferencing a pointer.
// If CanAddr returns false, calling Addr will panic.
func (v Value) CanAddr() bool {
   // flag & 100000000 != 0
   return v.flag&flagAddr != 0
}

CanAddr 报告 Value 的地址能不能通过 Addr() 获取,这样的 Value 称为 addressable,只有 slice 中的元素,数组中的元素,struct 的字段,指针指向的实例(Elem读取的值),才是 addressable 的,

简单理解就是能不能获取一个实例的地址,下面来试一试。

i := User{Name: "test"}
fmt.Println(reflect.ValueOf(i).CanAddr()) // false
fmt.Printf("%p\n", &i) // 0xc000092000
fmt.Println(reflect.ValueOf(&i).CanAddr()) // false
fmt.Println(reflect.ValueOf(&i).Elem().CanAddr()) // true
fmt.Printf("%p\n", reflect.ValueOf(&i).Elem().Addr().Interface()) // 0xc000092000,重新取出值的地址,可以看到地址是一样的

fmt.Println(reflect.ValueOf(&i).Elem().Field(1).CanAddr()) // true
fmt.Println(reflect.ValueOf(&i).Elem().Field(1).Addr()) // 0xc000092010

可以看到,在通过 Elem() 取到真实的实例后,通过 Addr 可以拿到实例的地址。

Addr()

Addr returns a pointer value representing the address of v. It panics if CanAddr() returns false. Addr is typically used to obtain a pointer to a struct field or slice element in order to call a method that requires a pointer receiver.
func (v Value) Addr() Value {
	if v.flag&flagAddr == 0 {
		panic("reflect.Value.Addr of unaddressable value")
	}
	return Value{v.typ.ptrTo(), v.ptr, v.flag.ro() | flag(Ptr)}
}

Addr 返回一个 Value 的指针地址。Value 必须是结构体字段、slice 元素或数组元素

参考资料