reflect
reflectTypeOfinterface{}ValueOf

概述

reflectTypeOfValueOfTypeValue

Go 语言反射的实现原理_Java

TypeTypeOfMethodByNameImplements
type Type interface {
       Align() int
       FieldAlign() int
       Method(int) Method
       MethodByName(string) (Method, bool)
       NumMethod() int
       Name() string
       PkgPath() string
       Size() uintptr
       String() string
       Kind() Kind
       Implements(u Type) bool
       ...
}
ValueTypeTypeValuereflectValue
type Value struct {
       // contains filtered or unexported fields
}

func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Float() float64
...
TypeValueTypeOfValueOfTypeValue

反射法则

运行时反射是程序在运行期间检查其自身结构的一种方式,它是 元编程 的一种,但是它带来的灵活性也是一把双刃剑,过量的使用反射会使我们的程序逻辑变得难以理解并且运行缓慢,我们在这一节中就会介绍 Go 语言反射的三大法则,这能够帮助我们更好地理解反射的作用。

  1. 从接口值可反射出反射对象;

  2. 从反射对象可反射出接口值;

  3. 要修改反射对象,其值必须可设置;

第一法则

reflect.TypeOfreflect.ValueOf
TypeOfauthorstringValueOfdraven
package main

import (
   "fmt"
   "reflect"
)

func main() {
   author := "draven"
   fmt.Println("TypeOf author:", reflect.TypeOf(author))
   fmt.Println("ValueOf author:", reflect.ValueOf(author))
}

$ go run main.go
TypeOf author: string
ValueOf author: draven
Method
StructFieldKey
TypeOfValueOf
reflect.ValueOf(1)intTypeOfValueOfinterface{}intinterface{}

第二法则

reflectInterface

Go 语言反射的实现原理_Java_02

Interfaceinterface{}
v := reflect.ValueOf(1)
v.Interface{}.(int)

这个过程就像从接口值到反射对象的镜面过程一样,从接口值到反射对象需要经过从基本类型到接口类型的类型转换和从接口类型到反射对象类型的转换,反过来的话,所有的反射对象也都需要先转换成接口类型,再通过强制类型转换变成原始类型:

Go 语言反射的实现原理_Java_03

interface{}

第三法则

reflect.Value
func main() {
   i := 1
   v := reflect.ValueOf(i)
   v.SetInt(10)
   fmt.Println(i)
}

$ go run reflect.go
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x82, 0x1014c0)
   /usr/local/go/src/reflect/value.go:247 +0x180
reflect.flag.mustBeAssignable(...)
   /usr/local/go/src/reflect/value.go:234
reflect.Value.SetInt(0x100dc0, 0x414020, 0x82, 0x1840, 0xa, 0x0)
   /usr/local/go/src/reflect/value.go:1606 +0x40
main.main()
   /tmp/sandbox590309925/prog.go:11 +0xe0
reflect: reflect.flag.mustBeAssignable using unaddressable value
reflect.ValueOfElemSetInt
func main() {
   i := 1
   v := reflect.ValueOf(&i)
   v.Elem().SetInt(10)
   fmt.Println(i)
}

$ go run reflect.go
10
reflect.ValueElem
func main() {
   i := 1
   v := &i
   *v = 10
}
ii*v

实现原理

reflect

类型和值

interface{}emptyInterfacertypeword
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}
TypeOfiemptyInterfacertype
func TypeOf(i interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i))
   return toType(eface.typ)
}

func toType(t *rtype) Type {
   if t == nil {
       return nil
   }
   return t
}
rtypeTypereflectName
func (t *rtype) String() string {
   s := t.nameOff(t.str).name()
   if t.tflag&tflagExtraStar != 0 {
       return s[1:]
   }
   return s
}
TypeOfinterface{}emptyInterface
ValueValueOfescapesunpackEfaceValue
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, e.word, f}
}
unpackEfaceinterface{}emptyInterfaceValue
TypeOfValueOfTypeOfValueOf
package main

import (
   "reflect"
)

func main() {
   i := 20
   _ = reflect.TypeOf(i)
}

$ go build -gcflags="-S -N" main.go
...
MOVQ    $20, ""..autotmp_20+56(SP) // autotmp = 20
LEAQ    type.int(SB), AX           // AX = type.int(SB)
MOVQ    AX, ""..autotmp_19+280(SP) // autotmp_19+280(SP) = type.int(SB)
LEAQ    ""..autotmp_20+56(SP), CX  // CX = 20
MOVQ    CX, ""..autotmp_19+288(SP) // autotmp_19+288(SP) = 20
...
-S -Nintautotmp_19+280(SP) ~ autotmp_19+288(SP)interface{}LEAQtype.int(SB)i
interface{}reflect

更新变量

reflect.ValueSetmustBeAssignablemustBeExported
func (v Value) Set(x Value) {
   v.mustBeAssignable()
   x.mustBeExported() // do not let unexported x leak
   var target unsafe.Pointer
   if v.kind() == Interface {
       target = v.ptr
   }
   x = x.assignTo("reflect.Set", v.typ, target)
   if x.flag&flagIndir != 0 {
       typedmemmove(v.typ, v.ptr, x.ptr)
   } else {
       *(*unsafe.Pointer)(v.ptr) = x.ptr
   }
}
SetassignToreflect.Value
func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value {
   if v.flag&flagMethod != 0 {
       v = makeMethodValue(context, v)
   }

   switch {
   case directlyAssignable(dst, v.typ):
       fl := v.flag&(flagAddr|flagIndir) | v.flag.ro()
       fl |= flag(dst.Kind())
       return Value{dst, v.ptr, fl}

   case implements(dst, v.typ):
       if target == nil {
           target = unsafe_New(dst)
       }
       if v.Kind() == Interface && v.IsNil() {
           return Value{dst, nil, flag(Interface)}
       }
       x := valueInterface(v, false)
       if dst.NumMethod() == 0 {
           *(*interface{})(target) = x
       } else {
           ifaceE2I(dst, x, target)
       }
       return Value{dst, target, flagIndir | flag(Interface)}
   }

   panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
}
assignToValueptr

实现协议

reflectImplements
reflect.TypeOf((*<interface>)(nil)).Elem()
CustomErrorerror
type CustomError struct{}

func (*CustomError) Error() string {
   return ""
}

func main() {
   typeOfError := reflect.TypeOf((*error)(nil)).Elem()
   customErrorPtr := reflect.TypeOf(&CustomError{})
   customError := reflect.TypeOf(CustomError{})

   fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true
   fmt.Println(customError.Implements(typeOfError)) // #=> false
}
CustomErrorerror*CustomError
func (t *rtype) Implements(u Type) bool {
   if u == nil {
       panic("reflect: nil type passed to Type.Implements")
   }
   if u.Kind() != Interface {
       panic("reflect: non-interface type passed to Type.Implements")
   }
   return implements(u.(*rtype), t)
}
Implementsimplements
func implements(T, V *rtype) bool {
   t := (*interfaceType)(unsafe.Pointer(T))
   if len(t.methods) == 0 {
       return true
   }

   // ...

   v := V.uncommon()
   i := 0
   vmethods := v.methods()
   for j := 0; j < int(v.mcount); j++ {
       tm := &t.methods[i]
       tmName := t.nameOff(tm.name)
       vm := vmethods[j]
       vmName := V.nameOff(vm.name)
       if vmName.name() == tmName.name() && V.typeOff(vm.mtyp) == t.typeOff(tm.typ) {
           if i++; i >= len(t.methods) {
               return true
           }
       }
   }
   return false
}
interface{}true

Go 语言反射的实现原理_Java_04

implementsijO(n+m)n + m

方法调用

reflectAdd(0, 1)
func Add(a, b int) int { return a + b }

func main() {
   v := reflect.ValueOf(Add)
   if v.Kind() != reflect.Func {
       return
   }
   t := v.Type()
   argv := make([]reflect.Value, t.NumIn())
   for i := range argv {
       if t.In(i).Kind() != reflect.Int {
           return
       }
       argv[i] = reflect.ValueOf(i)
   }
   result := v.Call(argv)
   if len(result) != 1 || result[0].Kind() != reflect.Int {
       return
   }
   fmt.Println(result[0].Int()) // #=> 1
}
reflect.ValueOfAddNumInargvreflect.ValueargvAddCall

使用反射来调用方法非常复杂,原本只需要一行代码就能完成的工作,现在需要 10 多行代码才能完成,但是这也是在静态语言中使用这种动态特性需要付出的成本,理解这个调用过程能够帮助我们深入理解 Go 语言函数和方法调用的原理。

func (v Value) Call(in []Value) []Value {
   v.mustBe(Func)
   v.mustBeExported()
   return v.call("Call", in)
}
CallMustBecall
reflect.Value
reflect

参数检查

unsafe.PointermethodReceiver
func (v Value) call(op string, in []Value) []Value {
   t := (*funcType)(unsafe.Pointer(v.typ))
   var (
       fn       unsafe.Pointer
       rcvr     Value
       rcvrtype *rtype
   )
   if v.flag&flagMethod != 0 {
       rcvr = v
       rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
   } else if v.flag&flagIndir != 0 {
       fn = *(*unsafe.Pointer)(v.ptr)
   } else {
       fn = v.ptr
   }

   n := t.NumIn()
   if len(in) < n {
       panic("reflect: Call with too few input arguments")
   }
   if len(in) > n {
       panic("reflect: Call with too many input arguments")
   }
   for i := 0; i < n; i++ {
       if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) {
           panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String())
       }
   }

   nin := len(in)
   if nin != t.NumIn() {
       panic("reflect.Value.Call: wrong argument count")
   }

除此之外,在参数检查的过程中我们还会检查当前传入参数的个数以及所有参数的类型是否能被传入该函数中,任何参数不匹配的问题都会导致当前函数直接 panic 并中止整个程序。

准备参数

当我们已经对当前方法的参数验证完成之后,就会进入函数调用的下一个阶段,为函数调用准备参数,在前面的章节 函数调用 中我们已经介绍过 Go 语言的函数调用的惯例,所有的参数都会被依次放置到堆栈上。

    nout := t.NumOut()
   frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)

   var args unsafe.Pointer
   if nout == 0 {
       args = framePool.Get().(unsafe.Pointer)
   } else {
       args = unsafe_New(frametype)
   }
   off := uintptr(0)

   if rcvrtype != nil {
       storeRcvr(rcvr, args)
       off = ptrSize
   }
   for i, v := range in {
       targ := t.In(i).(*rtype)
       a := uintptr(targ.align)
       off = (off + a - 1) &^ (a - 1)
       n := targ.size
       if n == 0 {
           v.assignTo("reflect.Value.Call", targ, nil)
           continue
       }
       addr := add(args, off, "n > 0")
       v = v.assignTo("reflect.Value.Call", targ, addr)
       if v.flag&flagIndir != 0 {
           typedmemmove(targ, addr, v.ptr)
       } else {
           *(*unsafe.Pointer)(addr) = v.ptr
       }
       off += n
   }
funcLayoutargsargsargsfuncLayouttypedmemmove

准备参数的过程其实就是计算各个参数和返回值占用的内存空间,并将所有的参数都拷贝内存空间对应的位置上。

调用函数

准备好调用函数需要的全部参数之后,就会通过以下的表达式开始方法的调用了,我们会向该函数中传入栈类型、函数指针、参数和返回值的内存空间、栈的大小以及返回值的偏移量:

    call(frametype, fn, args, uint32(frametype.size), uint32(retOffset))
runtime.reflectcall

处理返回值

args
    var ret []Value
   if nout == 0 {
       typedmemclr(frametype, args)
       framePool.Put(args)
   } else {
       typedmemclrpartial(frametype, args, 0, retOffset)

       ret = make([]Value, nout)
       off = retOffset
       for i := 0; i < nout; i++ {
           tv := t.Out(i)
           a := uintptr(tv.Align())
           off = (off + a - 1) &^ (a - 1)
           if tv.Size() != 0 {
               fl := flagIndir | flag(tv.Kind())
               ret[i] = Value{tv.common(), add(args, off, "tv.Size() != 0"), fl}
           } else {
               ret[i] = Zero(tv)
           }
           off += tv.Size()
       }
   }
   
   return ret
}
argsnoutargsreflect.Value
reflect.Valueret

总结

reflect