SoFunction
Updated on 2025-03-04

Example of learning how to use Go reflection

What is reflection

Most of the time, variables, types and functions in Go are very simple and direct. When a type, variable or function is needed, they can be defined directly:

type Foo struct {
 A int
 B string
}

var x Foo

func DoSomething(f Foo) {
 (, )
}

But sometimes you want to use variables at runtime that don't exist when writing the program. For example, you are trying to map data from a file or network request to a variable. Or you want to build a tool that works for different types. In this case, you need to use reflection. Reflection allows you to check types at runtime. It also allows you to check, modify and create variables, functions, and structures at runtime.

Reflection in Go is built on three concepts: Types, Types, and Values. The reflect package in the standard library provides an implementation of Go reflection.

Reflection variable type

First let's look at the types. You can use reflection to call the function varType := (var) to get the type of the variable var. This returns a variable of type with a set of methods that get various information about the type of the variable defined at the time. Let’s take a look at the commonly used methods of obtaining type information.

The first method we want to look at is Name(). This will return the name of the variable type. Some types (such as slices or pointers) do not have names, and this method returns an empty string.

The next method, and the first one I think, is Kind(). Type is composed of Kind ---Kind is an abstract representation of slices, maps, pointers, structures, interfaces, strings, arrays, functions, ints or other some kind of primitive type. It can be a bit tricky to understand the difference between Type and Kind, but think about it this way. If you define a struct named Foo, Kind is struct and type Foo.

One thing to note when using reflection: Everything in the reflection package assumes that you know what you are doing, and if used incorrectly, many function and method calls will cause panic. For example, if you call a method associated with a different type on the current type, your code will panic.

If the variable is a pointer, map, slice, channel, or array variable, you can use() to find out the type of value pointed to or contained.

If the variable is a structure, you can use reflection to get the number of fields in the structure and get the structure from each field. Provides you with the name, label, type, and structure tags for the field. where the tag information corresponds to the type of string, and it provides a Get method for parsing and extracting substrings in the tag information according to a specific key.

Here is a simple example for outputting type information for various variables:

type Foo struct {
  A int `tag1:"First Tag" tag2:"Second Tag"`
  B string
}

func main() {
  sl := []int{1, 2, 3}
  greeting := "hello"
  greetingPtr := &greeting
  f := Foo{A: 10, B: "Salutations"}
  fp := &f

  slType := (sl)
  gType := (greeting)
  grpType := (greetingPtr)
  fType := (f)
  fpType := (fp)

  examiner(slType, 0)
  examiner(gType, 0)
  examiner(grpType, 0)
  examiner(fType, 0)
  examiner(fpType, 0)
}

func examiner(t , depth int) {
  (("\t", depth), "Type is", (), "and kind is", ())
  switch () {
  case , , , , :
    (("\t", depth+1), "Contained type:")
    examiner((), depth+1)
  case :
    for i := 0; i < (); i++ {
      f := (i)
      (("\t", depth+1), "Field", i+1, "name is", , "type is", (), "and kind is", ())
      if  != "" {
        (("\t", depth+2), "Tag is", )
        (("\t", depth+2), "tag1 is", ("tag1"), "tag2 is", ("tag2"))
      }
    }
  }
}

The output of the type of variable is as follows:

Type is and kind is slice
   Contained type:
   Type is int and kind is int
 Type is string and kind is string
 Type is and kind is ptr
   Contained type:
   Type is string and kind is string
 Type is Foo and kind is struct
   Field 1 name is A type is int and kind is int
     Tag is tag1:"First Tag" tag2:"Second Tag"
     tag1 is First Tag tag2 is Second Tag
   Field 2 name is B type is string and kind is string
 Type is and kind is ptr
   Contained type:
   Type is Foo and kind is struct
     Field 1 name is A type is int and kind is int
       Tag is tag1:"First Tag" tag2:"Second Tag"
       tag1 is First Tag tag2 is Second Tag
     Field 2 name is B type is string and kind is string

Run in go playground: /p/lZ97yAUHxX

Create a new instance with reflection

In addition to checking the type of variable, reflection can also be used to read, set or create values. First, you need to create an instance of the variable using refVal := (var). If you want to be able to modify the value using reflection, you must use refPtrVal := (&var); to get a pointer to the variable. If you don't do this, you can use reflection to read the value, but you can't modify it.

Once an instance is available, you can use the Type() method to get the variable.

If you want to modify the value, remember that it must be a pointer and that it must be dereferenced first. Use().Set(newRefVal) to modify the value, and the value passed to Set() must also be.

If you want to create a new value, you can use the function newPtrVal := (varType) to implement it and pass in one. This will return a pointer value, which can then be modified using Elem().Set() as above.

Finally, you can go back to the normal variable value by calling the Interface() method. Since Go does not have generics, the original type of the variable is lost; this method returns a value of type interface{}. If a pointer is created so that the value can be modified, you need to dereference the reflected pointer using Elem().Interface(). In both cases, it is necessary to convert the empty interface to the actual type in order to use it.

The following code demonstrates these concepts:

type Foo struct {
  A int `tag1:"First Tag" tag2:"Second Tag"`
  B string
}

func main() {
  greeting := "hello"
  f := Foo{A: 10, B: "Salutations"}

  gVal := (greeting)
  // not a pointer so all we can do is read it
  (())

  gpVal := (&greeting)
  // it's a pointer, so we can change it, and it changes the underlying variable
  ().SetString("goodbye")
  (greeting)

  fType := (f)
  fVal := (fType)
  ().Field(0).SetInt(20)
  ().Field(1).SetString("Greetings")
  f2 := ().Interface().(Foo)
  ("%+v, %d, %s\n", f2, , )
}

Their output is as follows:

hello
goodbye
{A:20 B:Greetings}, 20, Greetings

Run in go playground /p/PFcEYfZqZ8

Reflection creates an instance of a reference type

In addition to generating instances of built-in and user-defined types, reflection can also be used to generate instances that usually require make functions. You can use, and functions to make slices, maps, or channels. In all cases, one is provided and then fetched, which can be operated on using reflection, or it can be assigned back to a standard variable.

func main() {
 // Define variables  intSlice := make([]int, 0)
  mapStringInt := make(map[string]int)

 // Get variable  sliceType := (intSlice)
  mapType := (mapStringInt)

  // Create a new instance of type using reflection  intSliceReflect := (sliceType, 0, 0)
  mapReflect := (mapType)

  // Assign the created new instance back to a standard variable  v := 10
  rv := (v)
  intSliceReflect = (intSliceReflect, rv)
  intSlice2 := ().([]int)
  (intSlice2)

  k := "hello"
  rk := (k)
  (rk, rv)
  mapStringInt2 := ().(map[string]int)
  (mapStringInt2)
}

Create functions using reflection

Reflection can not only create new places for storing data. You can also use functions to create new functions using reflect. This function expects the function we want to create, and a closure, whose input parameter is type [] and its return type is also type []. Here is a simple example that creates a timed wrapper for any function passed to it:

func MakeTimedFunction(f interface{}) interface{} {
  rf := (f)
  if () !=  {
    panic("expects a function")
  }
  vf := (f)
  wrapperF := (rf, func(in []) [] {
    start := ()
    out := (in)
    end := ()
    ("calling %s took %v\n", (()).Name(), (start))
    return out
  })
  return ()
}

func timeMe() {
  ("starting")
  (1 * )
  ("ending")
}

func timeMeToo(a int) int {
  ("starting")
  ((a) * )
  result := a * 2
  ("ending")
  return result
}

func main() {
  timed := MakeTimedFunction(timeMe).(func())
  timed()
  timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
  (timedToo(2))
}

You can run the code in goplayground/p/QZ8ttFZzGxAnd see the output as follows:

starting
ending
calling  took 1s
starting
ending
calling  took 2s
4

Reflection is a powerful tool that every Go developer should know and learn. But what can they do with them? In the next blog post, I will explore some usages of reflection in an existing library and use reflection to create something new.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.