Author: Jon Bodner | Address: Learning to Use Go Reflection
What is Reflection
In most cases, the use of variables, types and functions in Go is very simple.
When you need a type, define it as follows:
type Foo struct { A int B string }
When you need a variable, define it as follows:
var x Foo
When you need a function, define it as follows:
func DoSomething(f Foo) { fmt.Println(f.A, f.B) }
But sometimes, the variables you want to use depend on runtime information, and they don't exist in programming. For example, data comes from files or networks, and you want to map it to a variable, which may be of different types. In such scenarios, you need to use reflection. Reflection allows you to check types, create, update, check variables, and organize your organization at runtime.
Reflection in Go revolves around three concepts: Types, Kinds and Values. The source code for reflection implementation is located in the reflection package of the Go standard library.
Check type
First, let's look at Types. You can get the type of a variable through a function call in the form of reflect.TypeOf(var), which returns a variable of type reflect.Type. The operation method in reflect.Type involves all kinds of information defining the type of variable.
The first method we want to look at is Name(), which returns the name of the type. Some types, such as slice or pointer, will return empty strings if they have no type name.
The next introduction is Kind(), my point of view, which is the first really useful method. Kind is a category, such as slice, mapping map, pointer pointer, struct, interface, string, array, function, integer int, or other basic types. The difference between type and kind is not so easy to sort out, but you can think of it as follows:
When you define a structure called Foo, its kind is struct and its type is Foo.
When using reflection, we have to be aware that when using reflection packages, you assume that you know exactly what you are doing, and if you don't use it properly, panic will occur. For example, if you invoke the struct structure type only on int type, your code will generate panic. We should always remember what types and methods are available to avoid panic generation.
If a variable is a pointer, mapping, slice, pipe, or array type, then the type of the variable can call the method varType.Elem().
If a variable is a structure, you can use reflection to get the number of its fields, and you can get the information for each field, which is contained in the reflect.StructField structure. Reflection. StructField contains the name, sort, type, and label of the field.
The preface is not as clear as a line of code. The following example outputs information about the types of 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 := reflect.TypeOf(sl) gType := reflect.TypeOf(greeting) grpType := reflect.TypeOf(greetingPtr) fType := reflect.TypeOf(f) fpType := reflect.TypeOf(fp) examiner(slType, 0) examiner(gType, 0) examiner(grpType, 0) examiner(fType, 0) examiner(fpType, 0) } func examiner(t reflect.Type, depth int) { fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind()) switch t.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice: fmt.Println(strings.Repeat("\t", depth+1), "Contained type:") examiner(t.Elem(), depth+1) case reflect.Struct: for i := 0; i < t.NumField(); i++ { f := t.Field(i) fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind()) if f.Tag != "" { fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag) fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2")) } } } }
The output 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
Create an instance
In addition to checking the type of variable, you can also use it to get, set, and create variables. First, create an instance of type reflect.Value by refVal: = reflect. ValueOf (var). If you want to update the value by reflection, you must get the pointer refPtrVal: = reflect. ValueOf (& var) of the variable. If you don't, you can only read the value, not set the value.
Once you get the reflection. Value of the variable, you can get the reflection. Type type information of the variable through the Type attribute of the value.
If you want to update a value, remember to pass the pointer, and when setting it, first remove the reference, and update the value by refPtrVal.Elem().Set(newRefVal). The parameter passed to the Set must also be the reflect.Value type.
If you want to create a new variable, you can implement it by reflecting. New (varType), passing in a parameter of type reflect.Type. This method will return a pointer. As mentioned earlier, you can set its value by using Elem().Set().
Ultimately, through the Interface() method, you get a normal variable. Without generics in Go, the type of the variable will be lost, and the Interface() method will return a variable of type interface {}. If you create a pointer to update the value, you need to use Elem().Interface() to get the variable. But in either case, you need to convert the interface {} type variable to the actual type in order to use it.
Here are some code to implement 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 := reflect.ValueOf(greeting) // not a pointer so all we can do is read it fmt.Println(gVal.Interface()) gpVal := reflect.ValueOf(&greeting) // it's a pointer, so we can change it, and it changes the underlying variable gpVal.Elem().SetString("goodbye") fmt.Println(greeting) fType := reflect.TypeOf(f) fVal := reflect.New(fType) fVal.Elem().Field(0).SetInt(20) fVal.Elem().Field(1).SetString("Greetings") f2 := fVal.Elem().Interface().(Foo) fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B) }
The output is as follows:
hello goodbye {A:20 B:Greetings}, 20, Greetings
Creation examples without make
For types like slice, map, channel, they need to create instances with make, and you can also use reflection to implement them. Slice uses reflect.MakeSlice, map uses reflect.MakeMap, channel uses reflect.MakeChan. You need to provide the type of variable you create, reflect.Type, to pass to these functions. After successful invocation, you will get a variable of type reflect.Value. You can manipulate this variable by reflection, and when the operation is completed, you can convert it into a normal variable.
func main() { // declaring these vars, so I can make a reflect.Type intSlice := make([]int, 0) mapStringInt := make(map[string]int) // here are the reflect.Types sliceType := reflect.TypeOf(intSlice) mapType := reflect.TypeOf(mapStringInt) // and here are the new values that we are making intSliceReflect := reflect.MakeSlice(sliceType, 0, 0) mapReflect := reflect.MakeMap(mapType) // and here we are using them v := 10 rv := reflect.ValueOf(v) intSliceReflect = reflect.Append(intSliceReflect, rv) intSlice2 := intSliceReflect.Interface().([]int) fmt.Println(intSlice2) k := "hello" rk := reflect.ValueOf(k) mapReflect.SetMapIndex(rk, rv) mapStringInt2 := mapReflect.Interface().(map[string]int) fmt.Println(mapStringInt2) }
The output is as follows:
[10] map[hello:10]
Create functions
Not only can you create space to store data by reflection, but you can also create new functions by reflecting. MakeFunc, the function provided by reflection. This function expects to receive two parameters, one is reflect.Type, and Kind is Function, the other is closure function. Its input parameter type is [] reflect.Value, and its output parameter is [] reflect.Value.
Here's a quick-track example of wrapping a function that records execution time for any function.
func MakeTimedFunction(f interface{}) interface{} { rf := reflect.TypeOf(f) if rf.Kind() != reflect.Func { panic("expects a function") } vf := reflect.ValueOf(f) wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value { start := time.Now() out := vf.Call(in) end := time.Now() fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start)) return out }) return wrapperF.Interface() } func timeMe() { fmt.Println("starting") time.Sleep(1 * time.Second) fmt.Println("ending") } func timeMeToo(a int) int { fmt.Println("starting") time.Sleep(time.Duration(a) * time.Second) result := a * 2 fmt.Println("ending") return result } func main() { timed := MakeTimedFunction(timeMe).(func()) timed() timedToo := MakeTimedFunction(timeMeToo).(func(int) int) fmt.Println(timedToo(2)) }
The output is as follows:
starting ending calling main.timeMe took 1s starting ending calling main.timeMeToo took 2s 4
Create a new structure
In Go, reflection can also create a new structure at runtime, which you can implement by passing a slice of reflect.StructField to reflect.StructOf function. Does it sound absurd? We created a new type, but this type has no name, so it can not be converted into normal variables. You can create an instance through it and pass its value to a variable of type interface {} with Interface(), but if you want to set its value, you have to reflect it.
func MakeStruct(vals ...interface{}) interface{} { var sfs []reflect.StructField for k, v := range vals { t := reflect.TypeOf(v) sf := reflect.StructField{ Name: fmt.Sprintf("F%d", (k + 1)), Type: t, } sfs = append(sfs, sf) } st := reflect.StructOf(sfs) so := reflect.New(st) return so.Interface() } func main() { s := MakeStruct(0, "", []int{}) // this returned a pointer to a struct with 3 fields: // an int, a string, and a slice of ints // but you can't actually use any of these fields // directly in the code; you have to reflect them sr := reflect.ValueOf(s) // getting and setting the int field fmt.Println(sr.Elem().Field(0).Interface()) sr.Elem().Field(0).SetInt(20) fmt.Println(sr.Elem().Field(0).Interface()) // getting and setting the string field fmt.Println(sr.Elem().Field(1).Interface()) sr.Elem().Field(1).SetString("reflect me") fmt.Println(sr.Elem().Field(1).Interface()) // getting and setting the []int field fmt.Println(sr.Elem().Field(2).Interface()) v := []int{1, 2, 3} rv := reflect.ValueOf(v) sr.Elem().Field(2).Set(rv) fmt.Println(sr.Elem().Field(2).Interface()) }
The output is as follows:
0 20 reflect me [] [1 2 3]
Limitation of Reflection
Reflection has a big limitation. Although the runtime can create new functions by reflection, it is impossible to create new methods by reflection, which means that you can't implement an interface by reflection at runtime, and the structure created by reflection is fragmented. Furthermore, a feature of GO cannot be achieved by reflecting the constructs created - delegation mode through anonymous segments.
Look at an example of a delegation pattern implemented through a struct, where the field of the struct usually defines the name. In this example, we define two types, Foo and Bar:
type Foo struct { A int } func (f Foo) Double() int { return f.A * 2 } type Bar struct { Foo B int } type Doubler interface { Double() int } func DoDouble(d Doubler) { fmt.Println(d.Double()) } func main() { f := Foo{10} b := Bar{Foo: f, B: 20} DoDouble(f) // passed in an instance of Foo; it meets the interface, so no surprise here DoDouble(b) // passed in an instance of Bar; it works! }
The code shows that the Foo field in Bar has no name, which makes it an anonymous or embedded field. Bar also satisfies the Double interface, although only Foo implements the Double method, which is called delegation. At compile time, Go automatically generates methods in Foo for Bar. This is not inheritance. If you try to pass Bar to a function that only receives Foo, the compilation will not pass.
If you use reflection to create an embedded field and try to access it, there will be some very strange behavior. The best way is that we don't use it. On this issue, you can look at two issues of github. issue/15924 and issues/16522 . Unfortunately, they have not made any progress.
So what's the problem? What functions can we achieve if we support dynamic interfaces? As mentioned earlier, we can create functions through Go reflex, implement wrapping functions, and also through interface. In Java, this is called dynamic proxy. When it's combined with annotations, you get a very powerful ability to switch from imperative programming to declarative programming, for example JDBI This Java library allows you to define an interface at the DAO layer, and its SQL queries are defined by annotations. All data operation code is generated dynamically at run time, which is so powerful.
What's the point?
Even with this limitation, reflection is still a powerful tool that every Go developer should master. But how can we make good use of it? Next article,English original I will explore the use of reflection through some libraries, and use it to implement some functions.