Skip to content

Reflection

Reflection is a mechanism that allows inspecting the structure of a language itself at runtime. It can flexibly handle certain problems, but it also has obvious drawbacks, such as performance issues. In Go, reflection is closely related to interface{}. To a large extent, wherever interface{} appears, there will be reflection. The reflection API in Go is provided by the standard library reflect package.

Interfaces

Before we begin, let's briefly understand two interfaces in the runtime package. In Go, interfaces are essentially structs. Go divides interfaces into two major categories at runtime: interfaces without method sets and interfaces with method sets. For interfaces with method sets, they are represented by the following iface struct at runtime:

go
type iface struct {
   tab  *itab // contains data type, interface type, method set, etc.
   data unsafe.Pointer // pointer to the value
}

For interfaces without method sets, they are represented by the eface struct at runtime, as follows:

go
type eface struct {
   _type *_type // type
   data  unsafe.Pointer // pointer to the value
}

Both of these structs have corresponding struct types in the reflect package. iface corresponds to nonEmptyInterface:

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // static interface type
    typ  *rtype // dynamic concrete type
    hash uint32 // type hash
    _    [4]byte
    fun  [100000]unsafe.Pointer // method set
  }
  word unsafe.Pointer // pointer to the value
}

And eface corresponds to emptyInterface:

go
type emptyInterface struct {
   typ  *rtype // dynamic concrete type
   word unsafe.Pointer // value pointing to a pointer
}

For these two types, the official documentation provides clear definitions:

  • nonEmptyInterface: nonEmptyInterface is the header for an interface value with methods
  • emptyInterface: emptyInterface is the header for an interface{} value

The term "dynamic concrete type" mentioned above (original: dynamic concrete type) refers to the fact that Go is a 100% statically typed language. The word "static" is reflected in the abstract interface type that remains unchanged externally, while "dynamic" indicates that the concrete implementation type stored internally can vary. At this point, understanding the simple principles of interfaces to this extent is sufficient for subsequent learning about reflection.

Bridge

In the reflect package, there are reflect.Type interface type to represent types in Go, and reflect.Value struct type to represent values in Go:

go
type Type interface {
    ...

    Name() string

  PkgPath() string

  Size() uintptr

  String() string

  Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

The code above omits many details. For now, just understand the existence of these two types. All reflection-related operations in Go are based on these two types. The reflect package provides two functions to convert Go types into the above two types for reflection operations: reflect.TypeOf function:

go
func TypeOf(i any) Type

And reflect.ValueOf function:

go
func ValueOf(i any) Value

As you can see, both functions take parameters of type any, which is an alias for interface{}. If you want to perform reflection operations, you need to convert the type to interface{} first. This is why, as mentioned earlier, reflection cannot exist without the empty interface. Loosely speaking, the empty interface is the bridge connecting Go's type system with reflection. The following diagram vividly describes this process.

TIP

In the following text, for convenience, we will uniformly use the alias any to replace interface{}

Core

There are three classic reflection laws in Go. Combined with the content above, they are easy to understand:

  1. Reflection can convert variables of interface{} type into reflection objects

  2. Reflection can restore reflection objects back to interface{} type variables

  3. To modify a reflection object, its value must be settable

These three laws are the core of Go reflection. When you need to access type-related information, you use reflect.TypeOf. When you need to modify reflection values, you use reflect.ValueOf.

Types

reflect.Type represents types in Go. You can use the reflect.TypeOf() function to convert a variable to reflect.Type. Code example:

go
func main() {
  str := "hello world!"
  reflectType := reflect.TypeOf(str)
  fmt.Println(reflectType)
}

Output:

string

Kind

For Type, Go internally uses reflect.Kind to represent basic types in Go. It is essentially an unsigned integer uint.

go
type Kind uint

The reflect package uses Kind to enumerate all basic types in Go, as shown below:

go
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
   Pointer
   Slice
   String
   Struct
   UnsafePointer
)

The Kind type only implements the String() method of the Stringer interface. This type has only this one method. The return value of the String() method comes from an internal slice, as shown below. This syntax may look like a map at first glance, but it's actually a special syntax in Go: index expressions in slice literals.

go
var kindNames = []string{
   Invalid:       "invalid",
   Bool:          "bool",
   Int:           "int",
   Int8:          "int8",
   Int16:         "int16",
   Int32:         "int32",
   Int64:         "int64",
   Uint:          "uint",
   Uint8:         "uint8",
   Uint16:        "uint16",
   Uint32:        "uint32",
   Uint64:        "uint64",
   Uintptr:       "uintptr",
   Float32:       "float32",
   Float64:       "float64",
   Complex64:     "complex64",
   Complex128:    "complex128",
   Array:         "array",
   Chan:          "chan",
   Func:          "func",
   Interface:     "interface",
   Map:           "map",
   Pointer:       "ptr",
   Slice:         "slice",
   String:        "string",
   Struct:        "struct",
   UnsafePointer: "unsafe.Pointer",
}
go
type Type interface{
    Kind() Kind
}

Through Kind, you can know what basic type the value stored in the empty interface actually is. For example:

go
func main() {
    // Declare a variable of type any
  var eface any
    // Assign a value
  eface = 100
    // Get its type through the Kind method
  fmt.Println(reflect.TypeOf(eface).Kind())
}

Output:

int

Elem

go
type Type interface{
    Elem() Type
}

Using the Type.Elem() method, you can determine the element type stored in a data structure of type any. The acceptable underlying parameter types must be one of: pointer, slice, array, channel, or map. Otherwise, it will panic. Here's a code example:

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // Key() returns the reflection type of the map's key
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

Output:

string
int

A pointer can also be understood as a container. Using Elem() on a pointer will obtain the reflection type of the element it points to. Code example:

go
func main() {
  var eface any
    // Assign a pointer
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // Get the reflection type of the element the pointer points to
  vType := rType.Elem()
    // Output the package path
  fmt.Println(vType.PkgPath())
    // Output its name
  fmt.Println(vType.Name())
}
strings
Builder

Using arrays, slices, and channels works similarly.

Size

go
type Type interface{
    Size() uintptr
}

The Size method can be used to get the byte size occupied by the corresponding type. Example:

go
func main() {
  fmt.Println(reflect.TypeOf(0).Size())
  fmt.Println(reflect.TypeOf("").Size())
  fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
  fmt.Println(reflect.TypeOf(0.1).Size())
  fmt.Println(reflect.TypeOf([]string{}).Size())
}

Output:

8
16
16
8
24

TIP

Using unsafe.Sizeof() can achieve the same effect.

Comparable

go
type Type interface{
    Comparable() bool
}

The Comparable method can determine whether a type can be compared. Example:

go
func main() {
  fmt.Println(reflect.TypeOf("hello world!").Comparable())
  fmt.Println(reflect.TypeOf(1024).Comparable())
  fmt.Println(reflect.TypeOf([]int{}).Comparable())
  fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}

Output:

true
true
false
true

Implements

go
type Type interface{
    Implements(u Type) bool
}

The Implements method can determine whether a type implements a certain interface:

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}

Output:

true
false

ConvertibleTo

go
type Type interface{
    ConvertibleTo(u Type) bool
}

The ConvertibleTo method can determine whether a type can be converted to another specified type:

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}

Output:

true
false

Values

reflect.Value represents the value of a reflection interface. You can use the reflect.ValueOf() function to convert a variable to reflect.Value. Code example:

go
func main() {
  str := "hello world!"
  reflectValue := reflect.ValueOf(str)
  fmt.Println(reflectValue)
}

Output:

hello world!

Type

go
func (v Value) Type() Type

The Type method can get the type of a reflection value:

go
func main() {
   num := 114514
   rValue := reflect.ValueOf(num)
   fmt.Println(rValue.Type())
}

Output:

int

Elem

go
func (v Value) Elem() Value

Get the element reflection value of a reflection value:

go
func main() {
   num := new(int)
   *num = 114514
   // Take a pointer as an example
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

Output:

114514

Pointers

There are two ways to get the pointer of a reflection value:

go
// Returns a pointer reflection value representing the address of v
func (v Value) Addr() Value

// Returns a uintptr pointing to the raw value of v, equivalent to uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// Returns a uintptr pointing to the raw value of v
// Only when v's Kind is Chan, Func, Map, Pointer, Slice, UnsafePointer, otherwise panic
func (v Value) Pointer() uintptr

// Returns an unsafe.Pointer pointing to the raw value of v
// Only when v's Kind is Chan, Func, Map, Pointer, Slice, UnsafePointer, otherwise panic
func (v Value) UnsafePointer() unsafe.Pointer

Example:

go
func main() {
   num := 1024
   ele := reflect.ValueOf(&num).Elem()
   fmt.Println("&num", &num)
   fmt.Println("Addr", ele.Addr())
   fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
   fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
   fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}

Output:

&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058

TIP

fmt.Println will reflect to get the type of the parameter. If it's a reflect.Value type, it will automatically call Value.Interface() to get its original value.

Let's try it again with a map:

go
func main() {
  dic := map[string]int{}
  ele := reflect.ValueOf(&dic).Elem()
  println(dic)
  fmt.Println("Addr", ele.Addr())
  fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
  fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
  fmt.Println("UnsafePointer", ele.UnsafePointer())
}

Output:

0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0

Setting Values

go
func (v Value) Set(x Value)

If you want to modify a reflection value through reflection, its value must be addressable. In this case, you should modify its element value through a pointer, rather than directly trying to modify the element's value.

go
func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // Get the element the pointer points to
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

Output:

114514
11

Getting Values

go
func (v Value) Interface() (i any)

You can get the original value of a reflection value through the Interface() method:

go
func main() {
   var str string
   str = "hello"
   rValue := reflect.ValueOf(str)
   if v, ok := rValue.Interface().(string); ok {
      fmt.Println(v)
   }
}

Output:

hello

Functions

Through reflection, you can get all information about a function, and you can also call a function via reflection.

Information

Get all information about a function through reflection type:

go
func Max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

func main() {
   rType := reflect.TypeOf(Max)
   // Output function name; literal function types don't have a name
   fmt.Println(rType.Name())
   // Output the number of parameters and return values
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // Output the type of the first parameter
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // Output the type of the first return value
   fmt.Println(rResType.Kind())
}

Output:


2 1
int
int

Calling

Call a function through a reflection value:

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // Get the reflection value of the function
   rType := reflect.ValueOf(Max)
   // Pass in the parameter array
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

Output:

50

Structs

Suppose we have the following struct:

go
type Person struct {
  Name    string `json:"name"`
  Age     int    `json:"age"`
  Address string `json:"address"`
  money   int
}

func (p Person) Talk(msg string) string {
  return msg
}

Accessing Fields

The reflect.StructField struct is structured as follows:

go
type StructField struct {
  // Field name
  Name string
  // Package name
  PkgPath string
  // Type name
  Type      Type
  // Tag
  Tag       StructTag
  // Byte offset of the field
  Offset    uintptr
  // Index
  Index     []int
  // Whether it's an embedded field
  Anonymous bool
}

There are two ways to access struct fields: one is by index, and the other is by name.

go
type Type interface{
    Field(i int) StructField
}

Example of accessing by index:

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output the number of struct fields
  fmt.Println(rType.NumField())
  for i := 0; i < rType.NumField(); i++ {
    structField := rType.Field(i)
    fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
  }
}

Output:

4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 false
go
type Type interface{
    FieldByName(name string) (StructField, bool)
}

Example of accessing by name:

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // Output the number of struct fields
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

Output:

4
money int false

Modifying Fields

If you want to modify a struct field value, you must pass in a struct pointer. Here's an example of modifying a field:

go
func main() {
  // Pass in a pointer
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Get the field
  name := rValue.FieldByName("Name")
  // Modify the field value
  if (name != reflect.Value{}) { // If reflect.Value{} is returned, the field doesn't exist
    name.SetString("jack")
  }
  // Output the struct
  fmt.Println(rValue.Interface())
}

Output:

{jack 0  0}

For modifying private struct fields, some additional operations are required:

go
func main() {
  // Pass in a pointer
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Get a private field
  money := rValue.FieldByName("money")
  // Modify the field value
  if (money != reflect.Value{}) {
    // Construct a pointer reflection value pointing to the unexported field of the struct
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // Get the element the pointer points to, which is the field to be modified
    field := p.Elem()
    // Modify the value
    field.SetInt(164)
  }
  // Output the struct
  fmt.Printf("%+v\n", rValue.Interface())
}

Accessing Tags

After obtaining StructField, you can directly access its Tag:

go
// If it doesn't exist, ok is false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// If it doesn't exist, returns an empty string
func (tag StructTag) Get(key string) string

Example:

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   name, ok := rType.FieldByName("Name")
   if ok {
      fmt.Println(name.Tag.Lookup("json"))
      fmt.Println(name.Tag.Get("json"))
   }
}

Output:

name true
name

Accessing Methods

Accessing methods is very similar to accessing fields, except the function signatures are slightly different. The reflect.Method struct is as follows:

go
type Method struct {
  // Method name
  Name string
  // Package name
  PkgPath string
  // Method type
  Type  Type
  // The function corresponding to the method; the first parameter is the receiver
  Func  Value
  // Index
  Index int
}

Example of accessing method information:

go
func main() {
  // Get the struct reflection type
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output the number of methods
  fmt.Println(rType.NumMethod())
  // Iterate and output method information
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
  }
}

Output:

1
0 Talk func(main.Person, string) string true

If you want to get details about method parameters and return values, you can obtain them through Method.Func. The process is the same as accessing function information. Modifying the code above slightly:

go
func main() {
  // Get the struct reflection type
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output the number of methods
  fmt.Println(rType.NumMethod())
  // Iterate and output method information
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
    fmt.Println("Method parameters")
    for i := 0; i < method.Func.Type().NumIn(); i++ {
      fmt.Println(method.Func.Type().In(i).String())
    }
    fmt.Println("Method return values")
    for i := 0; i < method.Func.Type().NumOut(); i++ {
      fmt.Println(method.Func.Type().Out(i).String())
    }
  }
}

You can see that the first parameter is main.Person, which is the receiver type:

1
0 Talk func(main.Person, string) string true
Method parameters
main.Person
string
Method return values
string

Calling Methods

Calling a method is similar to calling a function, and you don't need to manually pass in the receiver. Example:

go
func main() {
   // Get the struct reflection type
   rValue := reflect.ValueOf(new(Person)).Elem()
   // Output the number of methods
   fmt.Println(rValue.NumMethod())
   // Iterate and output method information
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // Call the method and get the return value
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // Iterate and output return values
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

Output:

1
hello,reflect!

Creation

You can construct new values through reflection. The reflect package provides different convenient functions based on some special types.

Basic Types

go
// Returns a pointer reflection value pointing to the reflection value
func New(typ Type) Value

Using string as an example:

go
func main() {
   rValue := reflect.New(reflect.TypeOf(*new(string)))
   rValue.Elem().SetString("hello world!")
   fmt.Println(rValue.Elem().Interface())
}
hello world!

Structs

Struct creation also uses the reflect.New function:

go
type Person struct {
   Name    string `json:"name"`
   Age     int    `json:"age"`
   Address string `json:"address"`
   money   int
}

func (p Person) Talk(msg string) string {
   return msg
}

func main() {
   // Create struct reflection value
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

Output:

{ 0  0}

Slices

Create slices via reflection:

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // Create slice reflection value
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // Iterate and assign values
   for i := 0; i < 10; i++ {
      rValue.Index(i).SetInt(int64(i))
   }
   fmt.Println(rValue.Interface())
}
[0 1 2 3 4 5 6 7 8 9]

Map

Create maps via reflection:

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // Build map reflection value
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // Set values
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

Channels

Create channels via reflection:

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // Create channel reflection value
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

Functions

Create functions via reflection:

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // Pass in the wrapper type and function body
  fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
    for _, arg := range args {
      fmt.Println(arg.Interface())
    }
    return nil
  })
  fmt.Println(fn.Type())
  fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}

Output:

func(int)
1024

Deep Equality

reflect.DeepEqual is a function provided by the reflection package to determine whether two variables are completely equal. The signature is as follows:

go
func DeepEqual(x, y any) bool

This function handles each basic type. Here are some type judgment methods:

  • Arrays: Every element in the array is completely equal
  • Slices: When both are nil, they are judged as completely equal; or when both are non-empty, elements within the length range are completely equal
  • Structs: All fields are completely equal
  • Maps: When both are nil, they are completely equal; when both are non-nil, values mapped by each key are completely equal
  • Pointers: Point to the same element or point to completely equal elements
  • Interfaces: When the concrete types of the interfaces are completely equal
  • Functions: Only when both are nil are they completely equal; otherwise, they are not completely equal

Here are some examples:

Slices

go
func main() {
   a := make([]int, 100)
   b := make([]int, 100)
   fmt.Println(reflect.DeepEqual(a, b))
}

Output:

true

Structs

go
func main() {
   mike := Person{
      Name:   "mike",
      Age:    39,
      Father: nil,
   }

   jack := Person{
      Name:   "jack",
      Age:    18,
      Father: &mike,
   }

   tom := Person{
      Name:   "tom",
      Age:    18,
      Father: &mike,
   }
   fmt.Println(reflect.DeepEqual(mike, jack))
   fmt.Println(reflect.DeepEqual(tom, jack))
   fmt.Println(reflect.DeepEqual(jack, jack))
}

Output:

false
false
true

Golang by www.golangdev.cn edit