Reflection in Go

Understanding Reflection Theory

What is Reflection?

Reflection is the ability of a program to examine, introspect, and modify its own structure and behavior at runtime. In Go, reflection is provided through the reflect package, which allows you to work with types and values dynamically.

Go Reflection System interface{} reflect.TypeOf() reflect.ValueOf() Type • Kind() • Name() • NumField() Value • Interface() • CanSet() • Set() Type ↔ Value relationship Runtime Representation Type metadata + Value data Stored in interface{} structure

Type Information

Represents the static type of a value. Provides metadata about the type structure, methods, and properties without accessing the actual data.

Static Metadata

Value Information

Represents the runtime value and allows manipulation of the actual data. Can read, modify, and call methods on the underlying value.

Dynamic Mutable

Interface{} Role

The empty interface serves as the entry point for reflection, holding both type information and value data in a unified structure.

Container Universal

Reflection Basics

Reflection allows programs to examine and modify their structure and behavior at runtime.

Type and Value

package main

import (
    "fmt"
    "reflect"
)

func examineType(x interface{}) {
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)
    
    fmt.Printf("Type: %v\n", t)
    fmt.Printf("Kind: %v\n", t.Kind())
    fmt.Printf("Value: %v\n", v)
    fmt.Printf("Interface: %v\n", v.Interface())
    
    // Check properties
    fmt.Printf("Can set: %v\n", v.CanSet())
    fmt.Printf("Can addr: %v\n", v.CanAddr())
}

func main() {
    var x int = 42
    examineType(x)
    
    type Person struct {
        Name string
        Age  int
    }
    
    p := Person{"Alice", 30}
    examineType(p)
    
    // Examine pointer
    examineType(&p)
}

Struct Reflection

Inspect and manipulate struct fields dynamically.

type User struct {
    ID       int    \`json:"id" db:"user_id"\`
    Name     string \`json:"name" db:"full_name"\`
    Email    string \`json:"email" db:"email_address"\`
    IsActive bool   \`json:"is_active" db:"active"\`
    private  string // unexported field
}

func inspectStruct(s interface{}) {
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        v = v.Elem()
    }
    
    if t.Kind() != reflect.Struct {
        fmt.Println("Not a struct")
        return
    }
    
    fmt.Printf("Struct: %s\n", t.Name())
    fmt.Printf("Fields: %d\n", t.NumField())
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        
        fmt.Printf("\nField %d:\n", i)
        fmt.Printf("  Name: %s\n", field.Name)
        fmt.Printf("  Type: %s\n", field.Type)
        fmt.Printf("  Value: %v\n", value.Interface())
        fmt.Printf("  Exported: %v\n", field.IsExported())
        
        // Get tags
        if tag := field.Tag.Get("json"); tag != "" {
            fmt.Printf("  JSON tag: %s\n", tag)
        }
        if tag := field.Tag.Get("db"); tag != "" {
            fmt.Printf("  DB tag: %s\n", tag)
        }
    }
}

// Set struct fields dynamically
func setFieldByName(obj interface{}, fieldName string, value interface{}) error {
    v := reflect.ValueOf(obj)
    
    if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
        return fmt.Errorf("expected pointer to struct")
    }
    
    v = v.Elem()
    field := v.FieldByName(fieldName)
    
    if !field.IsValid() {
        return fmt.Errorf("field %s not found", fieldName)
    }
    
    if !field.CanSet() {
        return fmt.Errorf("field %s cannot be set", fieldName)
    }
    
    val := reflect.ValueOf(value)
    if field.Type() != val.Type() {
        return fmt.Errorf("type mismatch")
    }
    
    field.Set(val)
    return nil
}

Dynamic Method Calls

Call methods dynamically using reflection.

type Calculator struct {
    value float64
}

func (c *Calculator) Add(x float64) float64 {
    c.value += x
    return c.value
}

func (c *Calculator) Multiply(x float64) float64 {
    c.value *= x
    return c.value
}

func (c Calculator) GetValue() float64 {
    return c.value
}

func callMethod(obj interface{}, methodName string, args ...interface{}) ([]reflect.Value, error) {
    v := reflect.ValueOf(obj)
    method := v.MethodByName(methodName)
    
    if !method.IsValid() {
        return nil, fmt.Errorf("method %s not found", methodName)
    }
    
    // Convert arguments to reflect.Value
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }
    
    // Call method
    results := method.Call(in)
    return results, nil
}

// Generic function caller
func Call(fn interface{}, args ...interface{}) ([]interface{}, error) {
    v := reflect.ValueOf(fn)
    
    if v.Kind() != reflect.Func {
        return nil, fmt.Errorf("not a function")
    }
    
    t := v.Type()
    
    if len(args) != t.NumIn() {
        return nil, fmt.Errorf("wrong number of arguments")
    }
    
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        argValue := reflect.ValueOf(arg)
        argType := t.In(i)
        
        if !argValue.Type().AssignableTo(argType) {
            return nil, fmt.Errorf("argument %d type mismatch", i)
        }
        
        in[i] = argValue
    }
    
    out := v.Call(in)
    
    results := make([]interface{}, len(out))
    for i, val := range out {
        results[i] = val.Interface()
    }
    
    return results, nil
}

Creating Types Dynamically

Create new types and values at runtime.

// Create slice dynamically
func makeSlice(elemType reflect.Type, len, cap int) interface{} {
    sliceType := reflect.SliceOf(elemType)
    slice := reflect.MakeSlice(sliceType, len, cap)
    return slice.Interface()
}

// Create map dynamically
func makeMap(keyType, valueType reflect.Type) interface{} {
    mapType := reflect.MapOf(keyType, valueType)
    m := reflect.MakeMap(mapType)
    return m.Interface()
}

// Create channel dynamically
func makeChannel(elemType reflect.Type, buffer int) interface{} {
    chanType := reflect.ChanOf(reflect.BothDir, elemType)
    ch := reflect.MakeChan(chanType, buffer)
    return ch.Interface()
}

// Deep copy using reflection
func DeepCopy(src interface{}) interface{} {
    if src == nil {
        return nil
    }
    
    original := reflect.ValueOf(src)
    cpy := reflect.New(original.Type()).Elem()
    copyRecursive(original, cpy)
    
    return cpy.Interface()
}

func copyRecursive(src, dst reflect.Value) {
    switch src.Kind() {
    case reflect.Ptr:
        if !src.IsNil() {
            dst.Set(reflect.New(src.Elem().Type()))
            copyRecursive(src.Elem(), dst.Elem())
        }
    
    case reflect.Interface:
        if !src.IsNil() {
            dst.Set(reflect.ValueOf(DeepCopy(src.Interface())))
        }
    
    case reflect.Struct:
        for i := 0; i < src.NumField(); i++ {
            if dst.Field(i).CanSet() {
                copyRecursive(src.Field(i), dst.Field(i))
            }
        }
    
    case reflect.Slice:
        if !src.IsNil() {
            dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
            for i := 0; i < src.Len(); i++ {
                copyRecursive(src.Index(i), dst.Index(i))
            }
        }
    
    case reflect.Map:
        if !src.IsNil() {
            dst.Set(reflect.MakeMap(src.Type()))
            for _, key := range src.MapKeys() {
                val := src.MapIndex(key)
                newVal := reflect.New(val.Type()).Elem()
                copyRecursive(val, newVal)
                dst.SetMapIndex(key, newVal)
            }
        }
    
    default:
        dst.Set(src)
    }
}

Practical Applications

Real-world uses of reflection in Go applications.

// JSON-like encoder using reflection
func Encode(v interface{}) (map[string]interface{}, error) {
    result := make(map[string]interface{})
    
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    
    if val.Kind() != reflect.Struct {
        return nil, fmt.Errorf("expected struct")
    }
    
    typ := val.Type()
    
    for i := 0; i < val.NumField(); i++ {
        field := typ.Field(i)
        value := val.Field(i)
        
        if !field.IsExported() {
            continue
        }
        
        tag := field.Tag.Get("json")
        if tag == "-" {
            continue
        }
        
        name := field.Name
        if tag != "" {
            name = strings.Split(tag, ",")[0]
        }
        
        result[name] = value.Interface()
    }
    
    return result, nil
}

// Dependency injection container
type Container struct {
    services map[reflect.Type]interface{}
}

func NewContainer() *Container {
    return &Container{
        services: make(map[reflect.Type]interface{}),
    }
}

func (c *Container) Register(service interface{}) {
    t := reflect.TypeOf(service)
    c.services[t] = service
}

func (c *Container) Resolve(target interface{}) error {
    v := reflect.ValueOf(target)
    
    if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
        return fmt.Errorf("target must be pointer to struct")
    }
    
    v = v.Elem()
    t := v.Type()
    
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        fieldType := t.Field(i)
        
        tag := fieldType.Tag.Get("inject")
        if tag != "true" {
            continue
        }
        
        service, ok := c.services[fieldType.Type]
        if !ok {
            return fmt.Errorf("service not found for %s", fieldType.Type)
        }
        
        field.Set(reflect.ValueOf(service))
    }
    
    return nil
}

// Validation using reflection
func Validate(s interface{}) []string {
    var errors []string
    
    v := reflect.ValueOf(s)
    t := reflect.TypeOf(s)
    
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
        t = t.Elem()
    }
    
    for i := 0; i < v.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        
        tag := field.Tag.Get("validate")
        if tag == "" {
            continue
        }
        
        rules := strings.Split(tag, ",")
        for _, rule := range rules {
            if rule == "required" && isZero(value) {
                errors = append(errors, fmt.Sprintf("%s is required", field.Name))
            }
            
            if strings.HasPrefix(rule, "min=") {
                min, _ := strconv.Atoi(strings.TrimPrefix(rule, "min="))
                if value.Kind() == reflect.String && len(value.String()) < min {
                    errors = append(errors, fmt.Sprintf("%s must be at least %d characters", field.Name, min))
                }
            }
        }
    }
    
    return errors
}

func isZero(v reflect.Value) bool {
    return v.Interface() == reflect.Zero(v.Type()).Interface()
}

Advanced Reflection Patterns

Complex patterns that showcase the power of Go's reflection system.

// Generic JSON-like serializer using reflection
type Serializer struct {
    tagName string
    omitEmpty bool
}

func NewSerializer(tagName string) *Serializer {
    return &Serializer{tagName: tagName, omitEmpty: true}
}

func (s *Serializer) Serialize(v interface{}) (map[string]interface{}, error) {
    return s.serializeValue(reflect.ValueOf(v))
}

func (s *Serializer) serializeValue(v reflect.Value) (map[string]interface{}, error) {
    if v.Kind() == reflect.Ptr {
        if v.IsNil() {
            return nil, nil
        }
        v = v.Elem()
    }
    
    if v.Kind() != reflect.Struct {
        return nil, fmt.Errorf("expected struct, got %v", v.Kind())
    }
    
    result := make(map[string]interface{})
    t := v.Type()
    
    for i := 0; i < v.NumField(); i++ {
        field := t.Field(i)
        fieldValue := v.Field(i)
        
        if !field.IsExported() {
            continue
        }
        
        // Get field name from tag or use field name
        name := field.Name
        if tag := field.Tag.Get(s.tagName); tag != "" {
            parts := strings.Split(tag, ",")
            if parts[0] == "-" {
                continue
            }
            if parts[0] != "" {
                name = parts[0]
            }
        }
        
        // Handle different field types recursively
        switch fieldValue.Kind() {
        case reflect.Struct:
            nested, err := s.serializeValue(fieldValue)
            if err != nil {
                return nil, err
            }
            result[name] = nested
            
        case reflect.Slice, reflect.Array:
            arr := make([]interface{}, fieldValue.Len())
            for j := 0; j < fieldValue.Len(); j++ {
                elem := fieldValue.Index(j)
                if elem.Kind() == reflect.Struct {
                    nested, err := s.serializeValue(elem)
                    if err != nil {
                        return nil, err
                    }
                    arr[j] = nested
                } else {
                    arr[j] = elem.Interface()
                }
            }
            result[name] = arr
            
        default:
            result[name] = fieldValue.Interface()
        }
    }
    
    return result, nil
}

Performance Analysis

Understanding the performance implications of reflection in Go applications.

Reflection Overhead

// Slow: Using reflection
func SetFieldReflection(obj interface{}, name string, value interface{}) {
    v := reflect.ValueOf(obj).Elem()
    field := v.FieldByName(name)
    field.Set(reflect.ValueOf(value))
}

Performance: ~100-1000x slower than direct access

Direct Access

// Fast: Direct field access
func SetFieldDirect(obj *MyStruct, value string) {
    obj.Name = value
}

Performance: Compile-time optimized, minimal overhead

Operation Direct Access Reflection Performance Ratio Use Case
Field Read 1-2 ns/op 50-100 ns/op 50-100x slower Serialization, ORM
Field Write 1-2 ns/op 80-150 ns/op 80-150x slower Config binding, Injection
Method Call 5-10 ns/op 200-500 ns/op 40-100x slower RPC, Dynamic dispatch
Type Check 1 ns/op 20-50 ns/op 20-50x slower Type validation

Optimization Strategies

  • Cache reflection results: Store Type and Value objects for reuse
  • Batch operations: Group multiple reflection calls together
  • Compile-time generation: Use code generation instead of runtime reflection
  • Hybrid approaches: Combine interfaces with selective reflection
  • Profile first: Measure actual performance impact in your use case

Best Practices & Security

Performance Guidelines

  • Use reflection sparingly in hot paths
  • Cache Type and Value objects when possible
  • Prefer interfaces over reflection for polymorphism
  • Profile before optimizing

Safety Practices

  • Always check CanSet() before setting values
  • Handle nil pointers and interfaces carefully
  • Validate types before operations
  • Use recover() for panic protection

Security Considerations

  • Validate input when setting unexported fields
  • Sanitize method names from external input
  • Limit reflection usage in public APIs
  • Consider access control for sensitive operations

Common Anti-Patterns

  • Over-engineering: Using reflection when simple interfaces would suffice
  • Performance ignorance: Using reflection in performance-critical code without measurement
  • Poor error handling: Not properly handling reflection panics and errors
  • Type safety loss: Abandoning Go's type safety without good reason
  • Maintainability issues: Creating overly complex reflection-based code

When to Use Reflection

  • Serialization libraries: JSON, XML, Protocol Buffers marshaling
  • ORM systems: Database mapping and query building
  • Dependency injection: Framework-level configuration binding
  • Testing frameworks: Dynamic test discovery and execution
  • Plugin systems: Dynamic loading and interface discovery
  • Code generation tools: Analysis of existing code structures

Exercise Challenges

Practice Projects

Try implementing these reflection-based solutions to deepen your understanding:

1. Configuration Binder

Create a system that binds environment variables to struct fields using reflection and struct tags.

Beginner

2. Simple ORM

Build a basic ORM that can generate SQL queries and map results back to structs using reflection.

Intermediate

3. API Router

Implement an HTTP router that automatically registers handler methods based on struct tags and method signatures.

Advanced

4. Validation Framework

Create a comprehensive validation system using struct tags and reflection to validate complex nested structures.

Advanced