Metaprogramming in Golang: Reflection Package guide
Tags: go, metaprogramming
Published: 2023-11-15 08:41:59

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](https://pkg.go.dev/reflect#pkg-overview).