Reflection package is a very important feature in Golang because it allows metaprogramming, which is the capability of an application to examine its own structure. It works with types and it's used to analyze types at runtime. Types, Values and Kinds are the foundation of the reflect package. The reflection package allows you to extract the type and value from any interface{}
variable.
Let's implement the reflection package with examples.
reflect.TypeOf()
The TypeOf function is used to describe the reflect.type
value that is passed into the TypeOf method.
package main
import (
"fmt"
"reflect"
)
func main() {
var1 := "Reflection Package"
fmt.Println(reflect.TypeOf(var1))
var2 := 100
fmt.Println(reflect.TypeOf(var2))
var3 := true
fmt.Println(reflect.TypeOf(var3))
var4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(reflect.TypeOf(var4))
var5 := map[string]interface{}{"Name": "Paul", "Number": 1000}
fmt.Println(reflect.TypeOf(var5))
}
output
string
int
bool
[]int
map[string]interface {}
The output will show the various data types representation assigned by the variable. If the variable assigned is a string, the typeOf will be a string. If it is a Map variable the typeOf will show type Map and so on.
reflect.Value()
The reflect.value
is used to show the underlying value of the respective variable passed to reflect.ValueOf
method.
package main
import (
"fmt"
"reflect"
)
func main() {
var1 := "Reflection Package"
fmt.Println(reflect.ValueOf(var1))
var2 := 100
fmt.Println(reflect.ValueOf(var2))
var3 := true
fmt.Println(reflect.ValueOf(var3))
fmt.Println(reflect.ValueOf(&var3))
var4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(reflect.ValueOf(var4))
var5 := map[string]interface{}{"Name": "Paul", "Number": 1000}
fmt.Println(reflect.ValueOf(var5))
}
output
Reflection Package
100
true
0xc00001a0c0
[1 2 3 4 5 6 7 8 9 10]
map[Name:Paul Number:1000]
reflect.Kind()
Kind
is a property of reflect.Type
whose results display basic types and generic complex types. When using kind, for built in types Kind
and Type
will be the same but for the custom types they will differ.
package main
import (
"fmt"
"reflect"
)
func main() {
var1 := "Reflection Package"
fmt.Println(reflect.TypeOf(var1).Kind())
var2 := 100
fmt.Println(reflect.TypeOf(var2).Kind())
var3 := true
fmt.Println(reflect.TypeOf(var3).Kind())
var4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(reflect.TypeOf(var4).Kind())
var5 := map[string]interface{}{"Name": "Paul", "Number": 1000}
fmt.Println(reflect.TypeOf(var5).Kind())
}
output
string
int
bool
slice
map
reflect.Copy()
The copy method will copy contents from the source to the destination until either the source has been exhausted or the destination has been filled. Both Source and Destination must have a kind of slice or array and the elements in the slice or array must be of the same type. If the source has elements of string type the equivalent type of string should also be used in the destination. The same scenario will be applied to other types like boolean, integers, floats, and any other type.
package main
import (
"fmt"
"reflect"
)
func main() {
source := reflect.ValueOf([]int{1, 2, 3, 4})
destination := reflect.ValueOf([]int{5, 6, 7, 8})
// Copy() function retuns number of elements copied
counter := reflect.Copy(destination, source)
fmt.Println(counter)
fmt.Println(source)
fmt.Println(destination)
}
output
4
[1 2 3 4]
[1 2 3 4]
reflect.DeepEqual()
The DeepEqual
method will return either True or False if the value in your case X and Y types follow the following rules.
- Array values are deeply equal if their corresponding elements are deeply equal.
- Struct values are deeply equal if their corresponding fields both exported and unexported are deeply equal.
- Func values are deeply equal if both are nil otherwise they are not deeply equal.
- Interface values will be said to be deeply equal if they hold equal concrete deep values.
- For Map values deep equal applies when all of the following is true; they are both nil or both are non nil, they have the same length and either of them has the same Map object or corresponding Keys (matched using GO equality) map to deeply equal values.
- Pointer values are deeply equal if using
==
operator shows equality or if they point to deeply equal values. - Slice values are deep equal when they have the same length and both are nil or non nil.
- For other type values like strings, bools, numbers and channels they are deeply equal if using
==
operator shows equality.
package main
import (
"fmt"
"reflect"
)
type Employee struct {
name string
address int
email string
}
func main() {
// DeepEqual checks whether slices are are equal
slice1 := []string{"a", "b", "c", "d"}
slice2 := []string{"g", "h", "i", "j"}
result := reflect.DeepEqual(slice1, slice2)
fmt.Println(result)
slice3 := []string{"a", "b", "c", "d"}
slice4 := []string{"a", "b", "c", "d"}
result2 := reflect.DeepEqual(slice3, slice4)
fmt.Println(result2)
// DeepEqual will check whether the arrays are equal
arr1 := [3]int{10, 20, 30}
arr2 := [3]int{10, 20, 30}
result = reflect.DeepEqual(arr1, arr2)
fmt.Println(result)
// DeepEqual will check whether the structs are equal
emp1 := Employee{"Sagini", 10259, "sagini@gmail"}
emp2 := Employee{"Kamau", 6789, "kamau@gmail"}
result = reflect.DeepEqual(emp1, emp2)
fmt.Println(result)
}
output
false
true
true
false
reflect.Swapper()
The Swapper
method is used to swap elements in a given slice and will throw a panic should the underlying interface{}
provided not be a slice. Indexes of the respective values are used to do the swap.
package main
import (
"fmt"
"reflect"
)
func main() {
swapSlice := []int{1, 2, 3, 4, 5, 6, 7}
swap := reflect.Swapper(swapSlice)
fmt.Printf("Original slice is :%v\n", swapSlice)
// Swapper() function swaps elements in the provided slice using their index
swap(1, 6)
fmt.Printf("After the swap slice is :%v\n", swapSlice)
}
output
Original slice is :[1 2 3 4 5 6 7]
After the swap slice is :[1 7 3 4 5 6 2]
reflect.NumField()
The NumField
method returns the number of fields in a given struct.
package main
import (
"fmt"
"reflect"
)
type Employee struct {
Name string
Address int
Email string
}
func main() {
emp := Employee{"Geoffrey", 675757, "geoff@yahoo"}
empType := reflect.TypeOf(emp)
fmt.Println(empType.NumField())
}
output
3
reflect.Field()
The Field
method returns the reflect.value
of the field that is name of the field accessed and it's type at the specified index.
package main
import (
"fmt"
"reflect"
)
type Employee struct {
Name string
Address int
Email string
}
func main() {
emp := Employee{"Geoffrey", 675757, "geoff@yahoo"}
empType := reflect.TypeOf(emp)
// create a loop to loop over the struct
for i := 0; i < empType.NumField(); i++ {
field := empType.Field(i)
fmt.Println(field.Index, field.Name, field.Type)
}
}
output
[0] Name string
[1] Address int
[2] Email string
reflect.FieldByName()
The FieldByName
method is used to either get or set a struct field value by using name of a given field.
package main
import (
"fmt"
"reflect"
)
type Address struct {
Latitude int
Longitude int
Places string
}
func main() {
addr := Address{10, 30, "HeavensPark"}
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Latitude"))
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Longitude"))
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Places"))
reflect.ValueOf(&addr).Elem().FieldByName("Latitude").SetInt(20)
reflect.ValueOf(&addr).Elem().FieldByName("Longitude").SetInt(40)
reflect.ValueOf(&addr).Elem().FieldByName("Places").SetString("TreeTops")
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Latitude"))
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Longitude"))
fmt.Println(reflect.ValueOf(&addr).Elem().FieldByName("Places"))
}
output
10
30
HeavensPark
20
40
TreeTops
reflect.FieldByIndex()
The FieldByIndex
method will return a nested field corresponding to an index. A panic will occur if during the evaluation of struct, it finds a nil pointer or a field that is not a struct.
package main
import (
"fmt"
"reflect"
)
type Person struct {
firstName string
lastName string
}
type Account struct {
Person
accountName string
}
func main() {
acc := Account{
Person: Person{"Sagini", "Geoffrey"},
accountName: "Stanchart",
}
t := reflect.TypeOf(acc)
fmt.Printf("%#v\n", t.FieldByIndex([]int{0}))
fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 0}))
fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))
fmt.Printf("%#v\n", t.FieldByIndex([]int{1}))
}
output
reflect.StructField{Name:"Person", PkgPath:"", Type:(*reflect.rtype)(0x490ba0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"firstName", PkgPath:"main", Type:(*reflect.rtype)(0x48b4c0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"lastName", PkgPath:"main", Type:(*reflect.rtype)(0x48b4c0), Tag:"", Offset:0x10, Index:[]int{1}, Anonymous:false}
reflect.StructField{Name:"accountName", PkgPath:"main", Type:(*reflect.rtype)(0x48b4c0), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}
reflect.MakeSlice()
The MakeSlice
method creates a new slice value. From the created slice you can find its type, length and capacity.
package main
import (
"fmt"
"reflect"
)
func main() {
var intSlice []int
var intType reflect.Value = reflect.ValueOf(&intSlice)
createdSlice := reflect.MakeSlice(reflect.Indirect(intType).Type(), 5, 8)
fmt.Println("Kind of slice is:", createdSlice.Kind())
fmt.Println("Length of slice is:", createdSlice.Len())
fmt.Println("Capacity of slice is:", createdSlice.Cap())
}
output
Kind of slice is: slice
Length of slice is: 5
Capacity of slice is: 8
reflect.MakeMap()
The MakeMap
method will create a new Map of a specified type.
package main
import (
"fmt"
"reflect"
)
func main() {
var newMap map[string]interface{}
var mapType reflect.Value = reflect.ValueOf(&newMap)
mapCreated := reflect.MakeMap(reflect.Indirect(mapType).Type())
fmt.Println("Kind of map created is", mapCreated.Kind())
}
output
Kind of map created is map
reflect.MakeChan()
The MakeChan
method will create a new channel with a specified type and buffer-size. Since the created channel has a buffer size it is of type buffer channel
. Unbuffered channels
don't have a buffer size specified.
package main
import (
"fmt"
"reflect"
)
func main() {
var newChan chan string
var chanType reflect.Value = reflect.ValueOf(&newChan)
chanCreated := reflect.MakeChan(reflect.Indirect(chanType).Type(), 100)
fmt.Println("Kind of channel created is:", chanCreated.Kind())
fmt.Println("Capacity of channel create is:", chanCreated.Cap())
}
output
The kind of channel created is: chan
The capacity of channel created is: 100
reflect.MakeFunc()
The MakeFunc
method is used to create a new function value.
It gets the new function of the given Type that wraps the function fn.
package main
import (
"fmt"
"reflect"
)
type Addition func(int64, int64) int64
func main() {
typ := reflect.TypeOf(Addition(nil))
// verify if function. If not function return an error
if typ.Kind() != reflect.Func {
panic("A function is expected")
}
add := reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) {
a := args[0].Int()
b := args[1].Int()
return []reflect.Value{reflect.ValueOf(a + b)}
})
fn, ok := add.Interface().(Addition)
if !ok {
return
}
fmt.Println(fn(10, 5))
}
output
15
Conclusion
You have gone through how to use the reflection package in Golang. This is just the surface of its usage as it can be used in many areas of Golang. The advantage of Reflection is that it allows code to be flexible and handles unknown data types by analyzing their memory representation. However, this is not cost-free as it introduces complexity in code and slows down performance. You can learn more on reflection guidance here.
Top comments (0)