LanguagesUnderstanding Reflection in Go

Understanding Reflection in Go

Go’s reflection features enhance the expressiveness of the language and has been extensively used in the implementation of many APIs.The article provides an introductory idea on reflection and explains how it is handled in Go programming.

Overview of Reflection in Go

The mechanism through which we can update variables and inspect their values call methods without knowing anything about their type at runtime is called reflection. Reflection exposes the internals of the implementation and enables us to do various metaprogramming stuff. It provides code introspective abilities by which we can not only examine but also modify the inner working of the code. This powerful ability should be used responsibly and must exercise caution because it is also easy to subvert the protection provided by the language and compromise its integrity. Nonetheless, it has many uses.

Reflection is used extensively by standard Go libraries such as for string formatting provided by fmt, protocol encoding in packages like encoding/JSON and encoding/XML, implementing template mechanism provided by text/template and HTML/template. For example, the formatting logic within the fmt.Fprintf is a common example where reflection is used efficiently.

This function has the capability to deal uniformly with values of type that do not support a common interface and can print an arbitrary value of any type, even user-defined ones. Although the concept of reflection and introspection is used almost synonymously. Many programming languages claim to support reflection, whereas they actually support something called introspection. The difference is that the introspection enables low-level inspection of properties of various elements in a program; reflection not only does that but also empowers to modify them as well. Therefore, introspection is actually a subset of reflection.

Reflecting Type and Value in Go

The package that supports reflection in Go is called reflect. Using the API in this package we can easily find out the type of a variable. As we know any value is associated with two attributes: value and type. The value determines the content and the type determines the content type. There are two important structures called Type and Value that encapsulates the types and values of a variable respectively. Interestingly we can create either from the existing value. The function reflect.TypeOf() specifically tells us the type of a value and returns Type. For example:

a := 3.5
b := "hello"
c := 10
fmt.Printf("variable a : type=%v, value=%v\n", reflect.TypeOf(a), a)
fmt.Printf("variable b : type=%v, value=%v\n", reflect.TypeOf(b), b)
fmt.Printf("variable c : type=%v, value=%v\n", reflect.TypeOf(c), c)

This results in the following output:

Output
variable a : type=float64, value=3.5
variable b : type=string, value=hello
variable c : type=int, value=10

Similarly, there is a reflect.ValueOf() function to call on value and return reflect.Value.

str := "greetings"
greet := reflect.ValueOf(str).String()
fmt.Println(greet)

Here the output would be:

Output
greetings

Note that reflect.Value is a structure returned by reflect.ValueOf() function. Therefore if we want only the value of the variable we must use specific extracting method of the underlying type such as reflect.Value.Int() for integer, reflect.Value.Float() for float, reflect.Value.Bool() for boolean, reflect.Value.String() for string, reflect.Value.Complex() for complex values and so on.

Go does not support the creation of new types at run time but can construct new values or modify existing ones. The function reflect.TypeOf accepts any interface and returns its dynamic type as reflect.Type. Interestingly, as we assign a concrete value to an interface type, it implicitly converts to its type, which eventually has two components: a dynamic type and a dynamic value. For example, if we assign a concrete value(say, 3.56), the call to reflect.TypeOf() actually assigns the value to the interface{} parameter.

tt:=reflect.TypeOf(3.56)

There is a shorthand %T that uses reflect.TypeOf() internally and may be used to print the type. This is particularly useful for debugging and logging.

fmt.Printf("Type=%T\n", 3.56)

Similarly, the reflect.ValueOf() also accepts interface and returns reflect.Value as its dynamic content. Unlike reflect.TypeOf() the results of reflect.ValueOf() are always concrete but reflect.Value can contain interface values. A quick example as follows:

vv := reflect.ValueOf(3.56)
fmt.Printf("Value=%v", vv)

A shorthand %v may be used to print the content of reflect.Value returned by the call reflect.ValueOf().

Reflection with Collection Types using Go

The reflect package works in a similar manner with collection types such as slices and maps. It also works with structs. This ability is extensively used with JSON and XML encoders and decoders. For example, with structs, it works as follows:

package main
import (
  "fmt"
  "reflect"
)
func main() {
  type Employee struct {
    id int "check:range(1,999)"
    name string "check:len(3,20)"
  }
  emp1 := Employee{111, "Jerry"}
  sType := reflect.TypeOf(emp1)
  if sField, ok := sType.FieldByName("name"); ok {
    fmt.Printf("Type=%q field name=%q tag=%q\n", sField.Type, sField.Name, sField.Tag)
  }
}

This gives us the following output:

Output
Type="string" field name="name" tag="check:len(3,20)"

With slices, we can replace a given item in []string using introspection is given below. Normally, we may do this by directly assigning changed values at a specific indexed location. But with introspection, it looks like this:

weekdays := []string{"Sunday", "Monday", "Tuesday", "HOLIDAY", "Thursday", "Friday", "Saturday"}
sv := reflect.ValueOf(weekdays)
v := sv.Index(3)
fmt.Println("Before change: ", weekdays)
v.SetString("Wednesday")
fmt.Println("After change: ", weekdays)

Once more, here is the output:

Output
Before change: [Sunday Monday Tuesday HOLIDAY Thursday Friday Saturday]
After change: [Sunday Monday Tuesday Wednesday Thursday Friday Saturday]

Note that Go strings are immutable. Therefore, the value itself cannot be changed, but we can easily exchange an immutable value with another provided we have the address of the original value.

Go Reflection with Function and Methods

We can call arbitrary functions and methods using reflection. Here is an example where there is a function called IsPalindrome that checks if a string value is palindrome or not and returns a boolean value. In addition, there is another function called ReverseString that simply reverses a string. Here we have used the ReverseString function inside IsPalindrome function where we compared the original string with its reverse. If they are the same, then the string is a palindrome.

package main

import (
  "fmt"
  "reflect"
)

func ReverseString(s string) string {
  r := []rune(s)
  for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
    r[i], r[j] = r[j], r[i]
  }
  return string(r)
}

func IsPalindrome(s string) bool {
  flag := false
  if s == ReverseString(s) {
    flag = true
  }
  return flag
}

func main() {
  str := "madam"
  ret := IsPalindrome(str)
  fmt.Printf("Is '%s' Palindrome :%v\n", str, ret)

  fv := reflect.ValueOf(IsPalindrome)
  values := fv.Call([]reflect.Value{reflect.ValueOf(str)})
  ret = values[0].Bool()
  fmt.Printf("Is '%s' Palindrome :%v\n", str, ret)
}

This produces the following output:

Output
Is 'madam' Palindrome :true
Is 'madam' Palindrome :true

Observe that here we have called the IsPalindrome function twice. The first call is a normal function call, but in the second, we have used reflection to call the function. Although reflect.Value.Call returns a slice of []reflect.Value yet here we have passed a single value and hence got a single result.

Final Thoughts on Reflection in Go

A common situation where reflection is a way out is where we want to uniformly deal with values of types that don’t have a common interface, or there is no way to represent it uniformly. This is particularly the case with formatting logic within fmt.Fprintf. This useful function can print an arbitrary value of any type. The use of reflection by this function empowers it to handle the situation. There is a lot more to the reflection API in Go. Here we have merely scratched the surface to give a glimpse of it. Reflection is no doubt a powerful mechanism, but it should be used with care. As reflection works at runtime, mistakes here are not caught by the compiler, and a runtime error can be a disaster. Moreover, reflection reduces the safety and accuracy of automated refactoring and analysis tools. In fact, there are enough reasons not to use reflection as a new toy in a child’s hand but to be played responsibly by a seasoned programmer.

Editor’s Note: Read An Introduction to Concurrency in Go.

Latest Posts

Related Stories