Golang reflect Examples [Tutorial]

One of the more sophisticated Go issues is reflection. In today's post, I will try to explain reflection in Golang with some detail examples

 

Getting started with golang reflect package

Reflection: A program's ability to view its variables and values at runtime and determine their type is known as reflection.

Advertisement

reflect package: Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type. A call to ValueOf returns a Value representing the run-time data. Zero takes a Type and returns a Value representing a zero value for that type. See "The Laws of Reflection" for an introduction to reflection in Go: https://golang.org/doc/articles/laws_of_reflection.html

 

Why we need reflection

Go uses static typing. Every variable has a static type, which is a type that is known and fixed at compile time and is one of the following: int, float32,... For example:

	var i int
	var f float64
	var s string

Interface types, which express fixed sets of methods, are a crucial type category. Any concrete (non-interface) value may be stored in an interface variable as long as it implements the interface's methods. Io.Reader and Io.Writer, the types Reader and Writer from the io package, are a well-known pair of examples:

// Reader is the interface that wraps the basic Read method.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    Write(p []byte) (n int, err error)
}

Any type that implements a Read (or Write) method with this signature is said to implement io.Reader (or io.Writer). For the sake of this article, that means that any value whose type has a Read method can be stored in a variable of type io.Reader:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

Noted that: Go is statically typed and the static type of r is io.Reader.

An extremely important example of an interface type is the empty interface:

Advertisement
interface{}

or:

any

Since every value contains zero or more methods, it represents the empty set of methods and is satisfied by any value at all. Here is an example of using empty interface:

package main

import (
	"fmt"
)

func variableInfo(i interface{}) {

	// use the verb %T to check type
	fmt.Printf("The variable type is: %T\n", i)

	// use the verb %v to print the value
	fmt.Printf("The variable value is: %#v \n", i)
	fmt.Println("-------------------------------------")
}

func main() {
	var variable1 float64 = 10
	variable2 := 10
	variable3 := "Hello GolinuxCloud members!"
	variableInfo(variable1)
	variableInfo(variable2)
	variableInfo(variable3)
}

Output:

The variable type is: float64
The variable value is: 10 
-------------------------------------
The variable type is: int
The variable value is: 10
-------------------------------------
The variable type is: string
The variable value is: "Hello GolinuxCloud members!"
-------------------------------------

Conclusion: The data provided to these empty interfaces is frequently not primitive data. They might also be structs, for instance. Without knowing the nature or values of such data, we must run procedures on it. In this case, we need to know the kinds and the amount of fields contained in the struct in order to execute various actions on it, such as interpreting the data present in it to query a database or design a schema for a database. Reflection can be used at runtime to solve these issues.

 

Print type and value using reflect.TypeOf and reflect.ValueOf function

The concrete type of interface{} is represented by reflect.Type and the underlying value is represented by reflect.Value. Two functions reflect.TypeOf() and reflect.ValueOf() which return the reflect.Type and reflect.Value

func TypeOf(i any) Type: TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil

func ValueOf(i any) Value: ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value.

Here is an example of how to use TypeOf() and ValueOf() functions:

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	StudentId   int
	StudentName string
}

func printInfo(i interface{}) {
	// return type and value of the interface
	t := reflect.TypeOf(i)
	v := reflect.ValueOf(i)
	fmt.Println("Type of variable: ", t)
	fmt.Println("Value of varialbe:", v)

}
func main() {
	s1 := Student{
		StudentId:   1,
		StudentName: "Anna",
	}
	printInfo(s1)
	println("-----")
	printInfo(s1.StudentName)
}

Output:

Type of variable:  main.Student
Value of varialbe: {1 Anna}
-----
Type of variable:  string
Value of varialbe: Anna

 

Kind of variable in Golang

func (v Value) Kind() Kind: Kind returns v's Kind. If v is the zero Value (IsValid returns false), Kind returns Invalid.

The types Kind and Type in the reflection package may appear to be similar, but they differ, as the program below will show:

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	StudentId   int
	StudentName string
}

func printInfo(i interface{}) {
	// return type and value of the interface
	t := reflect.TypeOf(i)
	v := reflect.ValueOf(i)
	fmt.Println("Type of variable: ", t)
	fmt.Println("Value of varialbe:", v)

	// kind of value
	k := t.Kind()
	fmt.Println("Kind of variable:", k)

}
func main() {
	s1 := Student{
		StudentId:   1,
		StudentName: "Anna",
	}
	printInfo(s1)
	println("-----")
	printInfo(s1.StudentName)
}

Output:

Advertisement
Type of variable:  main.Student
Value of varialbe: {1 Anna}
Kind of variable: struct
-----
Type of variable:  string
Value of varialbe: Anna
Kind of variable: string

The actual type of the interface, in this case main, is represented by main.Student and Kind stand for a certain kind of the type. It is a struct in this instance.

 

NumField() and Field() functions

func (v Value) NumField() int: NumField returns the number of fields in the struct v. It panics if v's Kind is not Struct.

func (v Value) Field(i int) Value: Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range.

Here is an example of using NumField() and Field() function to get the struct information:

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	StudentId   int
	StudentName string
}

func printInfo(i interface{}) {
	if reflect.ValueOf(i).Kind() == reflect.Struct {
		// check if value is a Struct
		v := reflect.ValueOf(i)
		fmt.Println("Number of fields", v.NumField())
		for i := 0; i < v.NumField(); i++ {
			//print type and value of each field
			fmt.Printf("Field number:%d type:%T and value:%v\n", i, v.Field(i), v.Field(i))
		}
	} else {
		t := reflect.TypeOf(i)
		v := reflect.ValueOf(i)
		fmt.Println("Type of variable: ", t)
		fmt.Println("Value of varialbe:", v)

		// kind of value
		k := t.Kind()
		fmt.Println("Kind of variable:", k)
	}

}
func main() {
	s1 := Student{
		StudentId:   1,
		StudentName: "Anna",
	}
	printInfo(s1)
	println("-----")
	printInfo(s1.StudentName)
}

Output:

Number of fields 2
Field number:0 type:reflect.Value and value:1
Field number:1 type:reflect.Value and value:Anna
-----
Type of variable:  string
Value of varialbe: Anna
Kind of variable: string

 

Int(), String() and Float() functions

The reflect.VaXlue can be extracted as an int64, string or float64 using the methods Int(), String() and Float(), respectively:

Advertisement

func (v Value) Float() float64: Float returns v's underlying value, as a float64. It panics if v's Kind is not Float32 or Float64

func (v Value) Int() int64: Int returns v's underlying value, as an int64. It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64.

func (v Value) String() string: String returns the string v's underlying value, as a string. String is a special case because of Go's String method convention. Unlike the other getters, it does not panic if v's Kind is not String. Instead, it returns a string of the form "<T value>" where T is v's type.

Here is an example of using Int() and String() functions to print type and value of variable:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	intValue := 56
	stringValue := "58"
	intInfo := reflect.ValueOf(intValue).Int()
	// no panic
	intInfo2 := reflect.ValueOf(intValue).String()
	fmt.Printf("Variale type:%T, value:%v\n", intInfo, intInfo)
	fmt.Printf("Variale type:%T, value:%v\n", intInfo2, intInfo2)
	fmt.Printf("Variale type:%T, value:%v\n", intValue, intValue)

	stringInfo := reflect.ValueOf(stringValue).String()
	fmt.Printf("Variale type:%T, value:%v\n", stringInfo, stringInfo)
	fmt.Printf("Variale type:%T, value:%v\n", stringValue, stringValue)
}

Output:

Variale type:int64, value:56
Variale type:string, value:<int Value>
Variale type:int, value:56
Variale type:string, value:This is a string
Variale type:string, value:This is a string

Noted that, if we try to use Int() for a string value, the program will raise a panic:

Advertisement
	stringInfo := reflect.ValueOf(stringValue).String()
	// panic
	stringInfo2 := reflect.ValueOf(stringValue).Int()

 

Summary

Do you really need to reflect? Go's reflection concept is extremely powerful and sophisticated, thus it should be utilized with caution. Reflection makes it incredibly challenging to write understandable and maintainable code. It should only be used when absolutely required and avoided whenever possible. Hope with provided examples, you can understand about reflect concept in Golang

 

References

https://go.dev/blog/laws-of-reflection
https://pkg.go.dev/reflect

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X