Open In App

Reflection in Golang

Improve
Improve
Like Article
Like
Save
Share
Report

Reflection is the ability of a program to introspect and analyze its structure during run-time. In Go language, reflection is primarily carried out with types. The reflect package offers all the required APIs/Methods for this purpose. Reflection is often termed as a method of metaprogramming. To understand Reflection better let us get a primer on empty interfaces first:

“An interface that specifies zero methods is known as the empty interface.”

The empty interface is extremely useful when we are declaring a function with unknown parameters and data types. Library methods such as Println, Printf take empty interfaces as arguments. The empty interface has certain hidden properties that give it functionality. The data is abstracted in the following way.

Empty-Interface-in-Golang

Example 1: 

Go




// Understanding Empty Interfaces in Golang
package main
 
import (
    "fmt"
)
 
 
func observe(i interface{}) {
     
    // using the format specifier
    // %T to check type in interface
    fmt.Printf("The type passed is: %T\n", i)
     
    // using the format specifier %#v
    // to check value in interface
    fmt.Printf("The value passed is: %#v \n", i)
    fmt.Println("-------------------------------------")
}
 
func main() {
    var value float64 = 15
    value2 := "GeeksForGeeks"
    observe(value)
    observe(value2)
}


Output: 

The type passed is: float64
The value passed is: 15 
-------------------------------------
The type passed is: string
The value passed is: "GeeksForGeeks" 
-------------------------------------

Here we can clearly see that an empty interface will be able to take in any argument and adapt to its value and data type. This includes but is not limited to Structs and pointers to Structs.

What is the need for reflection?
Often times the data passed to these empty interfaces are not primitives. They might be structs as well for example. We need to perform procedures on such data without knowing their type or the values present in it. In such a situation in order to perform various operations on the struct, such as interpreting the data present in it to query a database or create a schema for a database we need to know the types present in it as well as the number of fields. These problems can be dealt with during run-time using reflection.

The reflect Package:
The foundation of Go reflection is based around Values, Types and Kinds.These are defined in the package and are of the type reflect.Value, reflect.Type and reflect.Kind and can be obtained using the methods 

  1. reflect.ValueOf(x interface{}).
  2. reflect.TypeOf(x interface{}).
  3. Type.Kind().
Type Kind
A Type is the representation of a type in Go.For example in user defined/custom types in go, the name assigned by the user is stored in as Type. A Kind is the representation of the type of a Type. For example in user defined/custom types, the data type of the Type will be the Kind.
The Type can be obtained using reflect.TypeOf(x interface{}) The Kind can be obtained using .Kind()

The reflect packages offers us a number of other methods:

  1. NumField(): This method returns the number of fields present in a struct. If the passed argument is not of the Kind reflect.Struct then it panics.
  2. Field(): This method allows us to access each field in the struct using an Indexing variable.

In the following example we will find the difference between the Kind and the Type of a struct and another custom data type. In addition to that we will use the methods from reflect package to get and print the fields from the struct and the value from the custom data type.

Example 2: 

Go




// Example program to show difference between
// Type and Kind and to demonstrate use of
// Methods provided by Go reflect Package
package main
 
import (
    "fmt"
    "reflect"
)
 
type details struct {
    fname   string
    lname   string
    age     int
    balance float64
}
 
type myType string
 
func showDetails(i, j interface{}) {
    t1 := reflect.TypeOf(i)
    k1 := t1.Kind()
    t2 := reflect.TypeOf(j)
    k2 := t2.Kind()
    fmt.Println("Type of first interface:", t1)
    fmt.Println("Kind of first interface:", k1)
    fmt.Println("Type of second interface:", t2)
    fmt.Println("Kind of second interface:", k2)
     
    fmt.Println("The values in the first argument are :")
    if reflect.ValueOf(i).Kind() == reflect.Struct {
        value := reflect.ValueOf(i)
        numberOfFields := value.NumField()
        for i := 0; i < numberOfFields; i++ {
            fmt.Printf("%d.Type:%T || Value:%#v\n",
              (i + 1), value.Field(i), value.Field(i))
             
            fmt.Println("Kind is ", value.Field(i).Kind())
        }
    }
    value := reflect.ValueOf(j)
    fmt.Printf("The Value passed in "+
      "second parameter is %#v", value)
}
 
func main() {
    iD := myType("12345678")
    person := details{
        fname:   "Go",
        lname:   "Geek",
        age:     32,
        balance: 33000.203,
    }
    showDetails(person, iD)
}


Output: 

Type of first interface: main.details
Kind of first interface: struct
Type of second interface: main.myType
Kind of second interface: string
The values in the first argument are :

1.Type:reflect.Value || Value:"Go"
Kind is  string
2.Type:reflect.Value || Value:"Geek"
Kind is  string
3.Type:reflect.Value || Value:32
Kind is  int
4.Type:reflect.Value || Value:33000.203
Kind is  float64
The Value passed in second parameter is "12345678"

In the above example, we pass two arguments to the function showDetails() which takes empty interfaces as parameters. The arguments are:

  1. person, which is a struct.
  2. iD, which is a string.

We have used the method reflect.TypeOf(i interface{}) and Type.Kind() methods to obtain those fields and we can see the difference in the output.

  • The struct person is of Type main.details and Kind reflect.Struct.
  • The variable iD is of Type main.myType and Kind string.

Hence the distinction between Type and Kind becomes clear and is according to their definitions. This is a fundamental concept in reflection in Go Language. Further, we have used the methods reflect.ValueOf(), .NumField() and .Field() from the reflect package as well to obtain the values in the empty interface, the number of fields of the struct and then each field separately. This is possible due to the use of reflection during run-time which allows us to determine the Type and Kind of arguments. 
Note :The NumField() and .Field() are only applicable to structs. A panic will be caused if the element is not a struct. The format specifier %T cannot be used to print Kind. It will print reflect.Kind if we pass i.Kind() in a Printf statement with a %T, it will print reflect.Kind which is essentially the Type of all Kinds in Go.
It is noteworthy that type of value.Field(i) is reflect.Value which is the Type and not the Kind. The Kind is displayed in the following line. Hence we see the importance and functionality of reflection in Go Lang. Knowing the types of variables during run-time enables us to write a lot of generic code. Therefore Reflection is an indispensable fundamental in Golang
 



Last Updated : 07 Sep, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads