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 xreflect.ValueOf(x)- Returns the reflect.Value of xvalue.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 valuereflect.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 structField(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 arrayIndex(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.ValuesMapIndex(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)