golang reflect实现原理

eudore · · 804 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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.TypeOfreflect.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库,通过这些原理就可以大概理解这些方法的作用和操作了,具体请参考源码。


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:eudore

查看原文:golang reflect实现原理

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

804 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传