Skip to main content
Go: Reflection for Runtime Type Inspection
  1. Posts/

Go: Reflection for Runtime Type Inspection

Roman
Author
Roman
Photographer with MSci in Computer Science and a Home Lab obsession
Table of Contents

Following the Learn Go with Tests guide, exploring Go’s reflection capabilities.

Unknown Types
#

Sometimes you need to:

  • Write generic functions before Go had generics
  • Serialize/deserialize arbitrary data structures
  • Build functions that work with any type
  • Inspect types at runtime

Go provides the empty interface interface{} to represent any type. In newer Go versions, any serves as a convenient alias for interface{}, making the code more readable.

Why Not Use interface{} Everywhere?
#

  • Type safety: The compiler can’t catch type errors at compile time
  • Documentation: Functions accepting interface{} don’t clearly communicate what types they expect
  • Complexity: You must use reflection to inspect and work with the actual type
  • Performance: Reflection operations are slower than direct type operations

Use interface{} and reflection only when you really need to.

Reflection
#

import (
    "reflect"
    "fmt"
)

func inspectValue(x interface{}) {
    v := reflect.ValueOf(x)
    t := reflect.TypeOf(x)
    
    fmt.Printf("Type: %v\n", t)
    fmt.Printf("Kind: %v\n", v.Kind())
    fmt.Printf("Value: %v\n", v)
}
  • reflect.TypeOf(x) - Returns the exact type of x
  • reflect.ValueOf(x) - Returns the reflect.Value of x
  • value.Kind() - Returns the general category/kind

Example outputs:

inspectValue("hello")
// Type: string
// Kind: string  
// Value: hello

inspectValue(42)
// Type: int
// Kind: int
// Value: 42

inspectValue([]string{"a", "b"})
// Type: []string
// Kind: slice
// Value: [a b]

type Person struct{ Name string }
inspectValue(Person{Name: "Alice"})
// Type: main.Person
// Kind: struct
// Value: {Alice}
  • reflect.Type - Static type information (string, int, []string, Person)
  • reflect.Value - Runtime value
  • reflect.Kind - Basic category (string, int, slice, struct, etc.)

Walk Function
#

A function that walks through any data structure and calls a function on string fields:

Helper Functions
#

walkValue := func(value reflect.Value) {
    walk(value.Interface(), fn)
}
  • Interface() - Converts a reflect.Value back to an interface{} for recursive calls
func getValue(x interface{}) reflect.Value {
    val := reflect.ValueOf(x) 

    if val.Kind() == reflect.Pointer {
        val = val.Elem()
    }

    return val
}
  • reflect.ValueOf(x) - Creates a reflect.Value from an interface{}
  • Elem() - Dereferences pointers and interfaces to get the underlying value

Handling Different Types
#

String Types
#

case reflect.String:
    fn(val.String())

Struct Types
#

case reflect.Struct:
    for i := 0; i < val.NumField(); i++ {
        walkValue(val.Field(i))
    }
  • NumField() - Returns the number of fields in the struct
  • Field(i) - Returns the reflect.Value of the field at index i

Slice and Array Types
#

case reflect.Slice, reflect.Array:
    for i := 0; i < val.Len(); i++ {
        walkValue(val.Index(i))
    }
  • Len() - Returns the length of the slice or array
  • Index(i) - Returns the reflect.Value of the element at index i

Map Types
#

case reflect.Map:
    for _, key := range val.MapKeys() {
        walkValue(val.MapIndex(key))
    }
  • MapKeys() - Returns a slice of all keys in the map as reflect.Values
  • MapIndex(key) - Returns the reflect.Value of the map value for the given key

Channel Types
#

case reflect.Chan:
    for {
        // ok is bool
        if v, ok := val.Recv(); ok {
            walkValue(v)
        } else {
            break
        }
    }
  • Recv() - Receives and returns a value from the channel

Function Types
#

case reflect.Func:
    valFnResult := val.Call(nil)
    for _, res := range valFnResult {
        walkValue(res)
    }
  • Call(nil) - Calls the function with no arguments and returns a slice of reflect.Values (the function’s return values)