Reflection and Type Switching in Golang

Golang Reflection

Hello there! So this is a side tangent for a deeper understanding of how and what reflection is, and how types and values in Go work. Be sure to check out the main article: Golang reflect package.

Simple Go Program

Say we have a simple code like this with a bunch of variables:

package main

import (
	"fmt"
)

type (
	ID     string
	Person struct {
		name string
	}
)

func main() {
	fmt.Println(true)
	fmt.Println(2010)
	fmt.Println(9.15)
	fmt.Println(7 + 7i)
	fmt.Println("Hello gophers!")
	fmt.Println(ID("6626068"))
	fmt.Println([5]byte{})
	fmt.Println([]byte{})
	fmt.Println(map[string]int{})
	fmt.Println(Person{name: "Jane Doe"})
	fmt.Println(&Person{name: "Jane Doe"})
	fmt.Println(make(chan int))

}

We have booleans, integers, floats, strings, maps, etc. So if we run this, we simply get:

Simple Function With Variables
Simple Function With Variables

Printing Variable Types

So this shall remain our main function, and we’ll add functions to fetch our types and values. We can easily also print out the type and value separately. We can just change the main function to Println2, and then define Println2 as:

// Println2 is my simple println function
func Println2(x interface{}) {
	fmt.Printf("type is '%T', value: %v\n", x, x)
}

This will give us a much better output:

Output Of New Print Func
Output Of New Print Func

So why are we doing this? What are we trying to achieve? And that is method overloading, the OOP paradigm that lets us define a method that has the same name but can perform different functions based on the variables passed into it. In Go, we’ll implement this using type switching.

Types and Values

Let’s now make a modification to Println2:

func Println2(x interface{}) {
	// x.type - for type information
	// x.value - for the value assigned
	if v, ok := x.(ID); ok {
		fmt.Printf("x has type ID, which I defined. The value is: %v\n", v)
	} else {
		fmt.Printf("'%T' is not the type I want\n", x)
	}
}

Here we are finally getting to use these types and values, and filter out the ones we want. Run the above program and see for yourself.

Also, focus on the input type – specifically the empty interface. It is a special data type, that is like an empty shell with two fields: Type and Value. So the end output of type is NOT an interface, but instead adaptive to whatever is passed.

Type Switching in Go

Let’s do some type switching:

func Println2(x interface{}) {
	switch x.(type) {
	case bool:
		fmt.Print("This is a boolean value: ", x.(bool))
	case int:
		fmt.Print("This is my nice int value: ", x.(int))
	case float64:
		fmt.Print(x.(float64))
	case complex128:
		fmt.Print(x.(complex128))
	case string:
		fmt.Print(x.(string))
	case Person:
		fmt.Print(x.(Person))
	case chan int:
		fmt.Print(x.(chan int))
	default:
		fmt.Print("Unknown type")
	}

	fmt.Print("\n")
}

Here, we’re using switch and case to check between the different types and using them.

This is exactly what the golang reflect package does.

The difference is that you are not only depending solely on the standard Go package, but also repeating the same switch and case again and again. So, therefore once we understand the concept, we can simply use reflect.

Reflection in Golang – reflect package

Comparing this with reflect:

func main() {
	var x interface{}
	x = 3.14

	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x) // x.(<type>)

	fmt.Printf("x: type = %v, value = %v\n", t, v)
	goo := x
	fmt.Printf("goo: type = %T, value = %v\n", goo, goo)

	x = &struct{ name string }{}

	t = reflect.TypeOf(x)
	v = reflect.ValueOf(x) // x.(<type>)
	fmt.Printf("x: type = %v, value = %v\n", t, v)
	hoo := x
	fmt.Printf("hoo: type = %T, value = %v\n", hoo, hoo)
}

we easily get:

Reflect Golang Output
Reflect Golang Output

References