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.
Example 1:
Go
package main
import (
"fmt"
)
func observe(i interface {}) {
fmt.Printf( "The type passed is: %T\n" , i)
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
- reflect.ValueOf(x interface{}).
- reflect.TypeOf(x interface{}).
- 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:
- 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.
- 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
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:
- person, which is a struct.
- 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
Similar Reads
Kotlin Reflection
Reflection is a set of language and library features that provides the feature of introspecting a given program at runtime. Kotlin reflection is used to utilize class and its members like properties, functions, constructors, etc. at runtime. Along with Java reflection API, Kotlin also provides its o
3 min read
Methods in Golang
Go methods are like functions but with a key difference: they have a receiver argument, which allows access to the receiver's properties. The receiver can be a struct or non-struct type, but both must be in the same package. Methods cannot be created for types defined in other packages, including bu
3 min read
Templates in GoLang
Template in Golang is a robust feature to create dynamic content or show customized output to the user. Golang has two packages with templates: text/template html/template There are mainly 3 parts of a template which are as follows: 1. Actions They are data evaluations, control structures like loops
3 min read
Import in GoLang
Pre-requisite learning: Installing Go on Windows / Installing Go on MacOS Technically defining, a package is essentially a container of source code for some specific purpose. This means that the package is a capsule that holds multiple elements of drug/medicine binding them all together and protecti
9 min read
What is Regex in Golang?
A Regular Expression (or RegEx) is a special sequence of characters that defines a search pattern that is used for matching specific text. In Golang, there's a built-in package for regular expressions, called the regexp package which contains all list of actions like filtering, replacing, validating
3 min read
Interfaces in Golang
In Go, an interface is a type that lists methods without providing their code. You canât create an instance of an interface directly, but you can make a variable of the interface type to store any value that has the needed methods. Exampletype Shape interface { Area() float64 Perimeter() float64}In
3 min read
Zero value in Golang
In Go language, whenever we allocate memory for a variable with the help of declaration or by using new and if the variable is not initialized explicitly, then the value of such types of variables are automatically initialized with their zero value. The initialization of the zero value is done recur
3 min read
Unidirectional Channel in Golang
As we know that a channel is a medium of communication between concurrently running goroutines so that they can send and receive data to each other. By default a channel is bidirectional but you can create a unidirectional channel also. A channel that can only receive data or a channel that can only
2 min read
Inheritance in GoLang
Inheritance means inheriting the properties of the superclass into the base class and is one of the most important concepts in Object-Oriented Programming. Since Golang does not support classes, so inheritance takes place through struct embedding. We cannot directly extend structs but rather use a c
3 min read
Parsing Time in Golang
Parsing time is to convert our time to Golang time object so that we can extract information such as date, month, etc from it easily. We can parse any time by using time.Parse function which takes our time string and format in which our string is written as input and if there is no error in our form
2 min read