Type Assertions and Reflection Boundaries

Overview

Type assertions extract concrete types from interfaces. Reflection provides runtime type inspection but should be used sparingly.

Type Assertions

var i interface{} = "hello"

s := i.(string)        // Panic if wrong type
s, ok := i.(string)    // Safe: ok is false if wrong type

if s, ok := i.(string); ok {
    fmt.Println(s)
}

Type Switches

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Println("int:", v*2)
    case string:
        fmt.Println("string:", len(v))
    case bool:
        fmt.Println("bool:", !v)
    default:
        fmt.Printf("unknown: %T\n", v)
    }
}

The reflect Package

import "reflect"

v := 42
t := reflect.TypeOf(v)   // int
val := reflect.ValueOf(v)  // 42

fmt.Println(t.Name())      // "int"
fmt.Println(t.Kind())      // int
fmt.Println(val.Int())     // 42

Struct Reflection

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

u := User{Name: "Alice", Age: 30}
t := reflect.TypeOf(u)

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    fmt.Printf("%s: %s\n", field.Name, field.Tag.Get("json"))
}

When to Use Reflection

Good uses: - Serialization (JSON, XML) - ORM field mapping - Generic testing utilities - Dependency injection

Avoid for: - Performance-critical code - Simple type checking - Normal business logic

Performance Cost

// Direct access: ~1ns
user.Name

// Reflection: ~100ns+
reflect.ValueOf(user).FieldByName("Name").String()

Prefer Generics Over Reflection

// Go 1.18+: Use generics instead of reflect
func PrintSlice[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}

Summary

Approach Use Case
Type assertion Extract known concrete type
Type switch Handle multiple types
reflect Runtime introspection (last resort)
Generics Compile-time type safety