1

Introduction

Reflection is a mechanism that allows you to see the composition of the structure and update the value without knowing the specific type at compile time. Using reflection allows us to write code that can handle all types in a unified manner. Even types that didn't exist when this part of the code was written. A specific example is the fmt.Println() method, which can print out our custom structure type.

Although, generally speaking, it is not recommended to use reflection in the code. Reflection affects performance, is not easy to read, and postpones type problems that can be checked at compile time to be manifested in panic form at runtime. These are the shortcomings of reflection. However, I think reflection must be mastered for the following reasons:

  • Many standard libraries and third-party libraries use reflection, although the exposed interface is encapsulated, and there is no need to understand reflection. But if you want to study these libraries in depth, understand the implementation, and read the source code, reflection cannot be bypassed. For example, encoding/json , encoding/xml etc.;
  • If there is a requirement to write a function or method that can handle all types, we must use reflection. Because the number of types in Go is unlimited and you can customize types, using type assertions cannot achieve your goal.

The Go language standard library reflect provides the reflection function.

interface

Reflection is built on Go's type system and is closely related to interfaces.

First, let's briefly introduce the interface. The interface in the Go language stipulates a set of methods, and any variable that defines the type of this set of methods (also known as implementing the interface) can be assigned to the variable of the interface.

package main

import "fmt"

type Animal interface {
  Speak()
}

type Cat struct {
}

func (c Cat) Speak() {
  fmt.Println("Meow")
}

type Dog struct {
}

func (d Dog) Speak() {
  fmt.Println("Bark")
}

func main() {
  var a Animal

  a = Cat{}
  a.Speak()

  a = Dog{}
  a.Speak()
}

In the above code, we defined a Animal interface, which agreed on a method Speak() . Then two structure types Cat and Dog are defined, both of which define this method. In this way, we can assign Cat and Dog objects to variables of type Animal

The interface variable contains two parts: type and value, namely (type, value) . The type is the type of the value assigned to the interface variable, and the value is the value assigned to the interface variable. If we know the type of variables stored in the interface, we can also use type assertions to get the value of a specific type through interface variables:

type Animal interface {
  Speak()
}

type Cat struct {
  Name string
}

func (c Cat) Speak() {
  fmt.Println("Meow")
}

func main() {
  var a Animal

  a = Cat{Name: "kitty"}
  a.Speak()

  c := a.(Cat)
  fmt.Println(c.Name)
}

In the above code, we know that the interface a saves the Cat object, and directly uses the type assertion a.(Cat) obtain the Cat object. However, if the type asserted does not match the actual stored type, it will panic directly. Therefore, in actual development, another type of assertion form c, ok := a.(Cat) is usually used. If the type does not match, this form will not panic, but by setting the second return value to false to indicate this situation .

Sometimes, a type defines many methods, not just the methods agreed upon by the interface. Through the interface, we can only call the method agreed in the interface. Of course, we can also assert its type as another interface, and then call the method agreed on this interface, provided that the original object implements this interface:

var r io.Reader
r = new(bytes.Buffer)
w = r.(io.Writer)

io.Reader and io.Writer are the two most frequently used interfaces in the standard library:

// src/io/io.go
type Reader interface {
  Read(p []byte) (n int, err error)
}

type Writer interface {
  Write(p []byte) (n int, err error)
}

bytes.Buffer implements these two interfaces at the same time, so the byte.Buffer object can be assigned to the io.Reader variable r , and then r can be asserted as io.Writer , because the value stored in the io.Reader io.Writer interface.

If an interface A contains another interface B all methods, the interface A variables can be assigned directly to B variables, because A value stored in certain implements A all the prescribed manner, then certainly achieved B . At this time, no type assertion is required. For example, the standard library io also defines a io.ReadCloser interface, this interface variable can be directly assigned to io.Reader :

// src/io/io.go
type ReadCloser interface {
  Reader
  Closer
}

empty interface interface{} is a special interface, it does not agree on any method. All types of values can be assigned to variables of the empty interface type, because it does not have any method to limit .

One point is particularly important. is type assertion or direct assignment between (type, value) type-value pair stored in its internal storage remains unchanged. It's just that the methods that can be called through different interfaces are different . For this reason, interface variable must not be the interface type .

With the basic knowledge of these interfaces, let's introduce reflection below.

Reflection basis

The reflection function in Go language is provided by the reflect package. reflect package defines an interface reflect.Type and a structure reflect.Value , which define a large number of methods for obtaining type information, setting values, and so on. Inside the reflect package, only the type descriptor implements the reflect.Type interface. Since the type descriptor is an unexported type, we can only get the value of the reflect.Type reflect.TypeOf()

package main

import (
  "fmt"
  "reflect"
)

type Cat struct {
  Name string
}

func main() {
  var f float64 = 3.5
  t1 := reflect.TypeOf(f)
  fmt.Println(t1.String())

  c := Cat{Name: "kitty"}
  t2 := reflect.TypeOf(c)
  fmt.Println(t2.String())
}

Output:

float64
main.Cat

Go language is statically typed, and each variable has and can only have one definite and known type at compile time, that is, the static type of the variable. The static type is determined when the variable is declared and cannot be modified. An interface variable, its static type is the interface type. Although different types of values can be assigned to it at runtime, only its internal dynamic type and dynamic value are changed. Its static type has never changed.

reflect.TypeOf() method is used to take out the dynamic type part of the interface and return it reflect.Type and many more! The above code does not seem to have an interface type?

Let's look at the definition of reflect.TypeOf()

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

It accepts a interface{} type 060be428aa10fc, so the above float64 and Cat variables will be converted to interface{} and then passed to the method. The reflect.TypeOf() method gets the type part in interface{}

Correspondingly, the reflect.ValueOf() method is naturally to obtain the value part of the interface, and the return value is of type reflect.Value Add the following code on the basis of the above example:

v1 := reflect.ValueOf(f)
fmt.Println(v1)
fmt.Println(v1.String())

v2 := reflect.ValueOf(c)
fmt.Println(v2)
fmt.Println(v2.String())

Run output:

3.5
<float64 Value>
{kitty}
<main.Cat Value>

Because fmt.Println() will do special processing for the reflect.Value type and print its internal value, it shows that the reflect.Value.String() method is called to obtain more information.

The acquisition type is so common, fmt provides the formatting symbol %T output parameter type:

fmt.Printf("%T\n", 3) // int

The types in Go are unlimited, and new types type However, the types of types are limited. All types of enumerations are defined in the reflect

// src/reflect/type.go
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
)

There are 26 kinds in total, and we can classify them as follows:

  • Basic types Bool , String and various numeric types (signed integer Int/Int8/Int16/Int32/Int64 , unsigned integer Uint/Uint8/Uint16/Uint32/Uint64/Uintptr , floating point number Float32/Float64 , complex number Complex64/Complex128 )
  • Composite (aggregate) types Array and Struct
  • Reference types Chan , Func , Ptr , Slice and Map (The distinction between value type and reference type is not obvious, and I don’t quote the conflict here, you just need to understand the meaning)
  • Interface type Interface
  • Illegal type Invalid , it represents no value ( reflect.Value zero value is the Invalid type)

All types in Go (including custom types) are the above types or their combination.

E.g:

type MyInt int

func main() {
  var i int
  var j MyInt

  i = int(j) // 必须强转

  ti := reflect.TypeOf(i)
  fmt.Println("type of i:", ti.String())

  tj := reflect.TypeOf(j)
  fmt.Println("type of j:", tj.String())

  fmt.Println("kind of i:", ti.Kind())
  fmt.Println("kind of j:", tj.Kind())
}

The static types of the above two variables are int and MyInt respectively, which are different. Although MyInt 's type (underlying type) is int . The assignment between them must be forced type conversion. But their types are the same, both are int .

The code output is as follows:

type of i: int
type of j: main.MyInt
kind of i: int
kind of j: int

Reflection usage

Since there are many reflection content and APIs, we will introduce them in combination with specific usage.

Pivot data composition

The composition of the perspective structure requires the following methods:

  • reflect.ValueOf() : Obtain the reflection value object;
  • reflect.Value.NumField() : Get the number of fields from the reflection value object of the structure;
  • reflect.Value.Field(i) : Obtain the reflection value object of the i field from the reflection value object of the structure;
  • reflect.Kind() : Obtain the type from the reflection value object;
  • reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool() : These methods extract the specific type from the reflection value object.

Example:

type User struct {
  Name    string
  Age     int
  Married bool
}

func inspectStruct(u interface{}) {
  v := reflect.ValueOf(u)
  for i := 0; i < v.NumField(); i++ {
    field := v.Field(i)
    switch field.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      fmt.Printf("field:%d type:%s value:%d\n", i, field.Type().Name(), field.Int())

    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      fmt.Printf("field:%d type:%s value:%d\n", i, field.Type().Name(), field.Uint())

    case reflect.Bool:
      fmt.Printf("field:%d type:%s value:%t\n", i, field.Type().Name(), field.Bool())

    case reflect.String:
      fmt.Printf("field:%d type:%s value:%q\n", i, field.Type().Name(), field.String())

    default:
      fmt.Printf("field:%d unhandled kind:%s\n", i, field.Kind())
    }
  }
}

func main() {
  u := User{
    Name:    "dj",
    Age:     18,
    Married: true,
  }

  inspectStruct(u)
}

Combination reflect.Value the NumField() and Field() methods may traverse each field structure. Then do the corresponding processing Kind each field.

Some methods can only be called when the original object is of a certain type. For example, the NumField() and Field() methods can only be called when the original object is a structure, otherwise it will be panic .

After identifying the specific type, you can call the corresponding type method of the reflection value object to obtain the value of the specific type, such as field.Int()/field.Uint()/field.Bool()/field.String() above. But in order to reduce the burden of processing, the Int()/Uint() method merges the types, they only return the corresponding types with the largest range, Int() returns the Int64 type, and Uint() returns the Uint64 type. And Int()/Uint() internally process the corresponding signed or unsigned type, and turn Int64/Uint64 back to 060be428aa1711. The following is the implementation reflect.Value.Int()

// src/reflect/value.go
func (v Value) Int() int64 {
  k := v.kind()
  p := v.ptr
  switch k {
  case Int:
    return int64(*(*int)(p))
  case Int8:
    return int64(*(*int8)(p))
  case Int16:
    return int64(*(*int16)(p))
  case Int32:
    return int64(*(*int32)(p))
  case Int64:
    return *(*int64)(p)
  }
  panic(&ValueError{"reflect.Value.Int", v.kind()})
}

In the above code, we only deal with a small number of categories. In actual development, perfect processing requires a lot of effort, especially when the field is of other complex types, or even contains circular references.

In addition, we can also see through the structure in the standard library, and we can see through the unexported field . Use the inspectStruct() method defined above:

inspectStruct(bytes.Buffer{})

bytes.Buffer is as follows:

type Buffer struct {
  buf      []byte
  off      int   
  lastRead readOp
}

They are all unexported fields, the program output:

field:0 unhandled kind:slice
field:1 type:int value:0
field:2 type:readOp value:0

To see the map , the following methods are required:

  • reflect.Value.MapKeys() reflect.Value objects of each key into a slice;
  • reflect.Value.MapIndex(k) : The reflect.Value object of the incoming key, and the return value of reflect.Value ;
  • Then you can perform the same processing as above for the key and value reflect.Value

Example:

func inspectMap(m interface{}) {
  v := reflect.ValueOf(m)
  for _, k := range v.MapKeys() {
    field := v.MapIndex(k)

    fmt.Printf("%v => %v\n", k.Interface(), field.Interface())
  }
}

func main() {
  inspectMap(map[uint32]uint32{
    1: 2,
    3: 4,
  })
}

I am here to lazy, not for each Kind do processing, direct call key - value reflect.Value of Interface() method. This method returns the value contained inside in the form of an empty interface. Program output:

1 => 2
3 => 4

Similarly, the MapKeys() and MapIndex(k) methods can only be called when the original object is of map , otherwise it will be panic .

For perspective slice or array composition, the following methods are required:

  • reflect.Value.Len() : Returns the length of the array or slice;
  • reflect.Value.Index(i) : Returns the value of reflect.Value i
  • Then the reflect.Value judgment Kind() is processed.

Example:

func inspectSliceArray(sa interface{}) {
  v := reflect.ValueOf(sa)

  fmt.Printf("%c", '[')
  for i := 0; i < v.Len(); i++ {
    elem := v.Index(i)
    fmt.Printf("%v ", elem.Interface())
  }
  fmt.Printf("%c\n", ']')
}

func main() {
  inspectSliceArray([]int{1, 2, 3})
  inspectSliceArray([3]int{4, 5, 6})
}

Program output:

[1 2 3 ]
[4 5 6 ]

Similarly, the Len() and Index(i) methods can only be called when the original object is a slice, array or string, and other types will be panic .

The perspective function type requires the following methods:

  • reflect.Type.NumIn() : Get the number of function parameters;
  • reflect.Type.In(i) : Get the i of the reflect.Type parameter;
  • reflect.Type.NumOut() : Get the number of function return values;
  • reflect.Type.Out(i) : Get the first i return value of reflect.Type .

Example:

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

func Greeting(name string) string {
  return "hello " + name
}

func inspectFunc(name string, f interface{}) {
  t := reflect.TypeOf(f)
  fmt.Println(name, "input:")
  for i := 0; i < t.NumIn(); i++ {
    t := t.In(i)
    fmt.Print(t.Name())
    fmt.Print(" ")
  }
  fmt.Println()

  fmt.Println("output:")
  for i := 0; i < t.NumOut(); i++ {
    t := t.Out(i)
    fmt.Print(t.Name())
    fmt.Print(" ")
  }
  fmt.Println("\n===========")
}

func main() {
  inspectFunc("Add", Add)
  inspectFunc("Greeting", Greeting)
}

NumIn()/In()/NumOut()/Out() only when the original object is a function type, and other types will be panic .

Program output:

Add input:
int int
output:
int
===========
Greeting input:
string
output:
string
===========

The method defined in the perspective structure requires the following methods:

  • reflect.Type.NumMethod() : Returns the number of methods defined by the structure;
  • reflect.Type.Method(i) : return the reflect.Method object of i

Example:

func inspectMethod(o interface{}) {
  t := reflect.TypeOf(o)

  for i := 0; i < t.NumMethod(); i++ {
    m := t.Method(i)

    fmt.Println(m)
  }
}

type User struct {
  Name    string
  Age     int
}

func (u *User) SetName(n string) {
  u.Name = n
}

func (u *User) SetAge(a int) {
  u.Age = a
}

func main() {
  u := User{
    Name:    "dj",
    Age:     18,
  }
  inspectMethod(&u)
}

reflect.Method defined as follows:

// src/reflect/type.go
type Method struct {
  Name    string // 方法名
  PkgPath string

  Type  Type  // 方法类型(即函数类型)
  Func  Value // 方法值(以接收器作为第一个参数)
  Index int   // 是结构体中的第几个方法
}

In fact, reflect.Value also defines these methods NumMethod()/Method(i) The difference is: reflect.Type.Method(i) returns a reflect.Method object, and you can get information about the method name, type, and the first method in the structure. If you want to reflect.Method , you must use the Func field, and you must pass in the receiver's reflect.Value as the first parameter:

m.Func.Call(v, ...args)

But reflect.Value.Method(i) return a reflect.Value objects, it always calls Method(i) method reflect.Value as a receiver objects, no additional incoming. And directly use Call() initiate a method call:

m.Call(...args)

reflect.Type and reflect.Value have many methods with the same name, so you need to pay attention to discrimination when using them.

Call function or method

To call a function, the following methods are required:

  • reflect.Value.Call() : Use reflect.ValueOf() generate reflection value objects for each parameter, and then compose slices and pass them to the Call() method. Call() method executes the function call and returns []reflect.Value . Each element is a reflection value object of the original return value.

Example:

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

func Greeting(name string) string {
  return "hello " + name
}

func invoke(f interface{}, args ...interface{}) {
  v := reflect.ValueOf(f)

  argsV := make([]reflect.Value, 0, len(args))
  for _, arg := range args {
    argsV = append(argsV, reflect.ValueOf(arg))
  }

  rets := v.Call(argsV)

  fmt.Println("ret:")
  for _, ret := range rets {
    fmt.Println(ret.Interface())
  }
}

func main() {
  invoke(Add, 1, 2)
  invoke(Greeting, "dj")
}

We encapsulate a invoke() method, interface{} empty interface, and receive function call parameters interface{} Inside the function, first call the reflect.ValueOf() method to obtain the reflection value object of the function object. reflect.ValueOf() for each parameter in turn to generate the reflection value object slice of the parameter. Call() method of the function reflection value object to output the return value.

Program running results:

ret:
3
ret:
hello dj

The method call is similar:

type M struct {
  a, b int
  op   rune
}

func (m M) Op() int {
  switch m.op {
  case '+':
    return m.a + m.b

  case '-':
    return m.a - m.b

  case '*':
    return m.a * m.b

  case '/':
    return m.a / m.b

  default:
    panic("invalid op")
  }
}

func main() {
  m1 := M{1, 2, '+'}
  m2 := M{3, 4, '-'}
  m3 := M{5, 6, '*'}
  m4 := M{8, 2, '/'}
  invoke(m1.Op)
  invoke(m2.Op)
  invoke(m3.Op)
  invoke(m4.Op)
}

operation result:

ret:
3
ret:
-1
ret:
30
ret:
4

The above is to initiate a call when the method name is clearly known at compile time. What if you only give a structure object and specify which method to call through a parameter? This requires the following methods:

  • reflect.Value.MethodByName(name) : Get the name structure defined name method of reflect.Value objects, this method has the default receiver parameters, i.e. calling MethodByName() method reflect.Value .

Example:

type Math struct {
  a, b int
}

func (m Math) Add() int {
  return m.a + m.b
}

func (m Math) Sub() int {
  return m.a - m.b
}

func (m Math) Mul() int {
  return m.a * m.b
}

func (m Math) Div() int {
  return m.a / m.b
}

func invokeMethod(obj interface{}, name string, args ...interface{}) {
  v := reflect.ValueOf(obj)
  m := v.MethodByName(name)

  argsV := make([]reflect.Value, 0, len(args))
  for _, arg := range args {
    argsV = append(argsV, reflect.ValueOf(arg))
  }

  rets := m.Call(argsV)

  fmt.Println("ret:")
  for _, ret := range rets {
    fmt.Println(ret.Interface())
  }
}

func main() {
  m := Math{a: 10, b: 2}
  invokeMethod(m, "Add")
  invokeMethod(m, "Sub")
  invokeMethod(m, "Mul")
  invokeMethod(m, "Div")
}

NumMethod() and Method() on the reflection value object of the structure to traverse all the methods it defines.

Actual case

Using the method introduced earlier, we can easily implement a simple HTTP-based RPC call. Conventional format: The path name /obj/method/arg1/arg2 calls the obj.method(arg1, arg2) method.

First define two structures and define methods for them. We agree that exportable methods will be registered as RPC methods. And the method must return two values: a result and an error.

type StringObject struct{}

func (StringObject) Concat(s1, s2 string) (string, error) {
  return s1 + s2, nil
}

func (StringObject) ToUpper(s string) (string, error) {
  return strings.ToUpper(s), nil
}

func (StringObject) ToLower(s string) (string, error) {
  return strings.ToLower(s), nil
}

type MathObject struct{}

func (MathObject) Add(a, b int) (int, error) {
  return a + b, nil
}

func (MathObject) Sub(a, b int) (int, error) {
  return a - b, nil
}

func (MathObject) Mul(a, b int) (int, error) {
  return a * b, nil
}

func (MathObject) Div(a, b int) (int, error) {
  if b == 0 {
    return 0, errors.New("divided by zero")
  }
  return a / b, nil
}

Next, we define a structure to indicate the RPC methods that can be called:

type RpcMethod struct {
  method reflect.Value
  args   []reflect.Type
}

Among them, method is the reflection value object of the method, and args is the type of each parameter. We define a function to extract methods from the object that can be called by RPC:

var (
  mapObjMethods map[string]map[string]RpcMethod
)

func init() {
  mapObjMethods = make(map[string]map[string]RpcMethod)
}

func registerMethods(objName string, o interface{}) {
  v := reflect.ValueOf(o)

  mapObjMethods[objName] = make(map[string]RpcMethod)
  for i := 0; i < v.NumMethod(); i++ {
    m := v.Method(i)

    if m.Type().NumOut() != 2 {
      // 排除不是两个返回值的
      continue
    }

    if m.Type().Out(1).Name() != "error" {
      // 排除第二个返回值不是 error 的
      continue
    }

    t := v.Type().Method(i)
    methodName := t.Name
    if len(methodName) <= 1 || strings.ToUpper(methodName[0:1]) != methodName[0:1] {
      // 排除非导出方法
      continue
    }

    types := make([]reflect.Type, 0, 1)
    for j := 0; j < m.Type().NumIn(); j++ {
      types = append(types, m.Type().In(j))
    }

    mapObjMethods[objName][methodName] = RpcMethod{
      m, types,
    }
  }
}

registerMethods() function uses reflect.Value.NumMethod() and reflect.Method(i) to traverse methods from the object, excluding methods that are not two return values, and the second return value is not error or non-exported.

Then define an http processor:

func handler(w http.ResponseWriter, r *http.Request) {
  parts := strings.Split(r.URL.Path[1:], "/")
  if len(parts) < 2 {
    handleError(w, errors.New("invalid request"))
    return
  }

  m := lookupMethod(parts[0], parts[1])
  if m.method.IsZero() {
    handleError(w, fmt.Errorf("no such method:%s in object:%s", parts[0], parts[1]))
    return
  }

  argSs := parts[2:]
  if len(m.args) != len(argSs) {
    handleError(w, errors.New("inconsistant args num"))
    return
  }

  argVs := make([]reflect.Value, 0, 1)
  for i, t := range m.args {
    switch t.Kind() {
    case reflect.Int:
      value, _ := strconv.Atoi(argSs[i])
      argVs = append(argVs, reflect.ValueOf(value))

    case reflect.String:
      argVs = append(argVs, reflect.ValueOf(argSs[i]))

    default:
      handleError(w, fmt.Errorf("invalid arg type:%s", t.Kind()))
      return
    }
  }

  ret := m.method.Call(argVs)
  err := ret[1].Interface()
  if err != nil {
    handleError(w, err.(error))
    return
  }

  response(w, ret[0].Interface())
}

We divide the path into a slice, the first element is the object name (that is, math or string ), the second element is the method name (that is, Add/Sub/Mul/Div etc.), and the rest are parameters. Next, we look for the method to be called, and convert the string in the path to the corresponding type according to the type of each parameter recorded during registration. Then call, check whether the second return value is nil to know whether the method call is wrong. A successful call will return the result.

Finally, we only need to start an http server:

func main() {
  registerMethods("math", MathObject{})
  registerMethods("string", StringObject{})

  mux := http.NewServeMux()
  mux.HandleFunc("/", handler)

  server := &http.Server{
    Addr:    ":8080",
    Handler: mux,
  }

  if err := server.ListenAndServe(); err != nil {
    log.Fatal(err)
  }
}

The complete code is in the Github repository. run:

$ go run main.go

Use curl to verify:

$ curl localhost:8080/math/Add/1/2
{"data":3}
$ curl localhost:8080/math/Sub/10/2
{"data":8}
$ curl localhost:8080/math/Div/10/2
{"data":5}
$ curl localhost:8080/math/Div/10/0
{"error":"divided by zero"}
$ curl localhost:8080/string/Concat/abc/def
{"data":"abcdef"}

Of course, this is only a simple implementation, and there are still many error handlings that have not been considered. The type of method parameters currently only supports int and string . If you are interested, you can improve it.

Settings

First introduce a concept: addressable. Addressable is the ability to obtain its address through reflection. Addressability is closely related to pointers. All reflect.ValueOf() obtained through reflect.Value are not addressable. Because they only save their own value and don't know anything about their address. For example, the pointer p *int saves the int data in the memory, but its own address cannot be obtained by itself, because when it is passed to reflect.ValueOf() , its own address information is lost. We can judge whether it is addressable reflect.Value.CanAddr()

func main() {
  x := 2

  a := reflect.ValueOf(2)
  b := reflect.ValueOf(x)
  c := reflect.ValueOf(&x)
  fmt.Println(a.CanAddr()) // false
  fmt.Println(b.CanAddr()) // false
  fmt.Println(c.CanAddr()) // false
}

Although the pointer is not addressable, we can call Elem() reflect.Value of the element it points to. This reflect.Value can be addressed, because it is reflect.Value.Elem() , and this acquisition path can be recorded. The resulting reflect.Value contains its address:

d := c.Elem()
fmt.Println(d.CanAddr())

In addition slice reflected by the object Index(i) obtained by the method reflect.Value is addressable, we can always get indexed by the address of a slice. The fields obtained through the pointer of the structure are also addressable:

type User struct {
  Name string
  Age  int
}

s := []int{1, 2, 3}
sv := reflect.ValueOf(s)
e := sv.Index(1)
fmt.Println(e.CanAddr()) // true

u := &User{Name: "dj", Age: 18}
uv := reflect.ValueOf(u)
f := uv.Elem().Field(0)
fmt.Println(f.CanAddr()) // true

If a reflect.Value is addressable, we can call its Addr() method to return a reflect.Value , which contains a pointer to the original data. Then call the Interface{} method on this reflect.Value , it will return a interface{} value containing this pointer. If we know the type, we can use type assertions to turn it into an ordinary pointer. To update the value through a normal pointer:

func main() {
  x := 2
  d := reflect.ValueOf(&x).Elem()
  px := d.Addr().Interface().(*int)
  *px = 3
  fmt.Println(x) // 3
}

Such a method of updating a bit of trouble, we can directly addressable reflect.Value call Set() to update the method, not through a pointer:

d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4

If the incoming type does not match, it will panic. reflect.Value Set methods for basic types SetInt , SetUint , SetFloat etc.:

d.SetInt(5)
fmt.Println(x) // 5

reflection can read the values of unexported structure fields, but cannot update these values. An addressable reflect.Value will record whether it is obtained by traversing an unexported field, and if it is, no modification is allowed. Therefore, it is not safe to CanAddr() determine before the update. CanSet() can correctly judge whether a value can be modified.

CanSet() judges the , which is a stricter property than the addressability. If a reflect.Value is settable, it must be addressable. The opposite is not true:

type User struct {
  Name string
  age  int
}

u := &User{Name: "dj", age: 18}
uv := reflect.ValueOf(u)
name := uv.Elem().Field(0)
fmt.Println(name.CanAddr(), name.CanSet()) // true true
age := uv.Elem().Field(1)
fmt.Println(age.CanAddr(), age.CanSet()) // true false

name.SetString("lidajun")
fmt.Println(u) // &{lidajun 18}
// 报错
// age.SetInt(20)

StructTag

When defining the structure, you can specify a label for each field, and we can use reflection to read these labels:

type User struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
}

func main() {
  u := &User{Name: "dj", Age: 18}
  t := reflect.TypeOf(u).Elem()
  for i := 0; i < t.NumField(); i++ {
    f := t.Field(i)
    fmt.Println(f.Tag)
  }
}

The label is an ordinary string, the output of the above program:

json:"name"
json:"age"

StructTag defined in the reflect/type.go file:

type StructTag string

The general practice is to separate each key-value pair with spaces, and use : between the key-values. E.g:

`json:"name" xml:"age"`

StructTag provides the Get() method to get the value corresponding to the key.

to sum up

This article systematically introduces the reflection mechanism in Go language, from type, interface to reflection usage. It also uses reflection to implement a simple HTTP-based RPC library. Although reflection is not recommended in normal development, you need to frequently use reflection knowledge when reading the source code and writing your own library. Proficiency in reflection can make reading the source code twice the result with half the effort.

If you find a fun and useful Go language library, welcome to submit an issue on the Go Daily Library GitHub😄

reference

  1. Rob Pike, laws of reflection: https://golang.org/doc/articles/laws_of_reflection.html
  2. Go Programming Language, Chapter 12: Reflection
  3. reflect official document, https://pkg.go.dev/reflect
  4. Go daily one library GitHub: https://github.com/darjun/go-daily-lib

I

My blog: https://darjun.github.io

Welcome to follow my WeChat public account [GoUpUp], learn together and make progress together~


darjun
2.9k 声望358 粉丝

引用和评论

0 条评论