Skip to content

🔮 Reflection & Metaprogramming

Reflection (Phản chiếu) là khả năng chương trình tự kiểm tra cấu trúc của chính nó tại runtime — inspect types, fields, values mà không cần biết trước type cụ thể.

🧠 The Concept: Tại sao cần Reflection?

Vấn đề: Static Typing Limitations

go
// Go là statically typed - compiler phải biết type
func PrintUser(u User) {
    fmt.Println(u.Name)  // Compiler biết User có field Name
}

// Nhưng nếu muốn function xử lý BẤT KỲ struct nào?
func PrintAny(v ???) {  // Type gì? interface{}?
    fmt.Println(v.???)   // Không biết có field gì!
}

Giải pháp: Reflection

go
import "reflect"

// Reflection cho phép inspect struct tại runtime
func PrintAny(v interface{}) {
    val := reflect.ValueOf(v)
    typ := reflect.TypeOf(v)
    
    for i := 0; i < val.NumField(); i++ {
        fieldName := typ.Field(i).Name
        fieldValue := val.Field(i).Interface()
        fmt.Printf("%s: %v\n", fieldName, fieldValue)
    }
}

// Works với BẤT KỲ struct nào!
PrintAny(User{Name: "Raizo", Age: 30})
PrintAny(Order{ID: 123, Total: 99.99})

Real-world Use Cases

Library/FeatureSử dụng Reflection để...
encoding/jsonMap JSON keys → struct fields
database/sqlScan rows → struct fields
GORM/sqlxRead struct tags cho column mapping
ValidatorCheck validation tags
Dependency InjectionAuto-wire dependencies by type

🔬 The reflect Package: Type vs Value

Core Concepts

go
import "reflect"

type User struct {
    ID   int64  `json:"id" db:"user_id"`
    Name string `json:"name"`
}

u := User{ID: 42, Name: "Raizo"}

// reflect.TypeOf - Thông tin về TYPE
t := reflect.TypeOf(u)
fmt.Println(t.Name())      // "User"
fmt.Println(t.Kind())      // "struct"
fmt.Println(t.NumField())  // 2

// reflect.ValueOf - Thông tin về VALUE
v := reflect.ValueOf(u)
fmt.Println(v.Field(0))    // 42
fmt.Println(v.Field(1))    // "Raizo"

Visual Comparison

┌─────────────────────────────────────────────────────────────────────┐
│                    reflect.TypeOf vs reflect.ValueOf               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   User{ID: 42, Name: "Raizo"}                                      │
│                                                                     │
│   ┌────────────────────────────┐  ┌────────────────────────────┐   │
│   │     reflect.TypeOf(u)      │  │    reflect.ValueOf(u)      │   │
│   │     (reflect.Type)         │  │    (reflect.Value)         │   │
│   ├────────────────────────────┤  ├────────────────────────────┤   │
│   │ Name(): "User"             │  │ Field(0): 42               │   │
│   │ Kind(): struct             │  │ Field(1): "Raizo"          │   │
│   │ NumField(): 2              │  │ Interface(): User{...}     │   │
│   │ Field(i): StructField      │  │ CanSet(): false            │   │
│   │   - Name, Type, Tag        │  │ Elem(): (nếu pointer)      │   │
│   └────────────────────────────┘  └────────────────────────────┘   │
│                                                                     │
│   TYPE trả lời: "Đây là cái gì?"  VALUE trả lời: "Giá trị là gì?" │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Kind vs Type

go
type UserID int64
type OrderID int64

// TypeOf - returns actual type name
reflect.TypeOf(UserID(1)).Name()   // "UserID"
reflect.TypeOf(OrderID(1)).Name()  // "OrderID"

// Kind - returns underlying kind
reflect.TypeOf(UserID(1)).Kind()   // reflect.Int64
reflect.TypeOf(OrderID(1)).Kind()  // reflect.Int64

// Kind categories:
// reflect.Int, Int8, Int16, Int32, Int64
// reflect.Uint, Uint8, ...
// reflect.Float32, Float64
// reflect.String, Bool
// reflect.Slice, Array, Map, Chan
// reflect.Struct, Ptr, Interface, Func

✏️ Modifying Values: Elem() và CanSet()

The Problem: Reflection Returns Copies

go
u := User{Name: "Raizo"}
v := reflect.ValueOf(u)

v.Field(0).SetString("Tom")  // 💥 PANIC!
// panic: reflect: reflect.Value.SetString using unaddressable value

🔥 Raizo's Warning: Why Panics Happen

Reflection operates on copies by default!

go
u := User{Name: "Raizo"}
v := reflect.ValueOf(u)  // v holds COPY of u

v.CanSet()  // false - không thể modify copy!

Để modify được, cần truyền POINTER:

go
u := User{Name: "Raizo"}
v := reflect.ValueOf(&u)  // Truyền pointer

v.CanSet()        // false (v là pointer, không phải value)
v.Elem().CanSet() // true! Elem() dereferences pointer

The Solution: Pointer + Elem()

go
func ModifyField(structPtr interface{}, fieldName string, newValue interface{}) error {
    v := reflect.ValueOf(structPtr)
    
    // Step 1: Check if pointer
    if v.Kind() != reflect.Ptr {
        return errors.New("must pass pointer to struct")
    }
    
    // Step 2: Dereference to get actual struct
    v = v.Elem()
    
    if v.Kind() != reflect.Struct {
        return errors.New("must pass pointer to struct")
    }
    
    // Step 3: Find field
    field := v.FieldByName(fieldName)
    if !field.IsValid() {
        return fmt.Errorf("field %s not found", fieldName)
    }
    
    // Step 4: Check if settable
    if !field.CanSet() {
        return fmt.Errorf("field %s cannot be set (unexported?)", fieldName)
    }
    
    // Step 5: Set value
    newVal := reflect.ValueOf(newValue)
    if field.Type() != newVal.Type() {
        return fmt.Errorf("type mismatch: want %v, got %v", field.Type(), newVal.Type())
    }
    
    field.Set(newVal)
    return nil
}

// Usage
u := User{Name: "Raizo"}
ModifyField(&u, "Name", "Tom")
fmt.Println(u.Name)  // "Tom" - modified!

Elem() Chaining

┌─────────────────────────────────────────────────────────────────────┐
│                       Pointer Dereferencing                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   &User{Name: "Raizo"}                                             │
│          │                                                          │
│          ▼                                                          │
│   reflect.ValueOf(&u)                                              │
│   ┌─────────────────────────────────────────┐                       │
│   │  v.Kind() = Ptr                         │                       │
│   │  v.CanSet() = false                     │                       │
│   │  v.Elem() ─────────────────────────┐    │                       │
│   └────────────────────────────────────┼────┘                       │
│                                        │                            │
│                                        ▼                            │
│                           ┌─────────────────────────────────────┐   │
│                           │  elem.Kind() = Struct               │   │
│                           │  elem.CanSet() = true ✅            │   │
│                           │  elem.FieldByName("Name").Set(...)  │   │
│                           └─────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

🏷️ Struct Tags Analysis

How ORMs Use Struct Tags

go
type User struct {
    ID        int64     `json:"id" db:"user_id" gorm:"primaryKey"`
    FirstName string    `json:"first_name" db:"first_name"`
    Email     string    `json:"email" db:"email" validate:"email"`
    Password  string    `json:"-" db:"password_hash"`
    CreatedAt time.Time `json:"created_at" db:"created_at"`
}

Reading Tags with Reflection

go
func AnalyzeStructTags(v interface{}) {
    t := reflect.TypeOf(v)
    
    // Handle pointer
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    
    if t.Kind() != reflect.Struct {
        fmt.Println("Not a struct")
        return
    }
    
    fmt.Printf("Struct: %s\n", t.Name())
    fmt.Println(strings.Repeat("-", 60))
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        
        fmt.Printf("Field: %s\n", field.Name)
        fmt.Printf("  Type: %s\n", field.Type)
        fmt.Printf("  JSON: %s\n", field.Tag.Get("json"))
        fmt.Printf("  DB:   %s\n", field.Tag.Get("db"))
        fmt.Printf("  GORM: %s\n", field.Tag.Get("gorm"))
        fmt.Println()
    }
}

// Output:
// Struct: User
// ------------------------------------------------------------
// Field: ID
//   Type: int64
//   JSON: id
//   DB:   user_id
//   GORM: primaryKey
// ...

HPN Use Case: SQL Query Builder

go
// Simplified ORM-style query builder
func BuildInsertSQL(tableName string, v interface{}) (string, []interface{}) {
    val := reflect.ValueOf(v)
    typ := reflect.TypeOf(v)
    
    if typ.Kind() == reflect.Ptr {
        val = val.Elem()
        typ = typ.Elem()
    }
    
    var columns []string
    var placeholders []string
    var values []interface{}
    
    for i := 0; i < typ.NumField(); i++ {
        field := typ.Field(i)
        dbTag := field.Tag.Get("db")
        
        // Skip if no db tag or explicitly ignored
        if dbTag == "" || dbTag == "-" {
            continue
        }
        
        columns = append(columns, dbTag)
        placeholders = append(placeholders, fmt.Sprintf("$%d", len(placeholders)+1))
        values = append(values, val.Field(i).Interface())
    }
    
    sql := fmt.Sprintf(
        "INSERT INTO %s (%s) VALUES (%s)",
        tableName,
        strings.Join(columns, ", "),
        strings.Join(placeholders, ", "),
    )
    
    return sql, values
}

// Usage
user := User{ID: 1, FirstName: "Raizo", Email: "raizo@hpn.dev"}
sql, args := BuildInsertSQL("users", user)
// sql: "INSERT INTO users (user_id, first_name, email) VALUES ($1, $2, $3)"
// args: [1, "Raizo", "raizo@hpn.dev"]

⚠️ Performance & Panic Risks

Performance Cost

🔥 Raizo's Critical Warning: Reflection is SLOW

go
// Benchmark comparison
// BenchmarkDirectAccess-8      500,000,000    2.5 ns/op
// BenchmarkReflection-8          5,000,000  285.0 ns/op
// → Reflection ~100x slower!

type User struct {
    Name string
    Age  int
}

// Direct Access (Fast)
func GetNameDirect(u User) string {
    return u.Name  // 2.5 ns
}

// Reflection (Slow)
func GetNameReflect(v interface{}) string {
    return reflect.ValueOf(v).FieldByName("Name").String()  // 285 ns
}

Memory overhead:

┌─────────────────────────────────────────────────────────────────────┐
│                    PERFORMANCE COMPARISON                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   Direct Access:                                                    │
│   ────────────────────────────────────────────────────────────     │
│   CPU: ████ 2.5 ns                                                 │
│   Alloc: 0 bytes                                                    │
│                                                                     │
│   Reflection:                                                       │
│   ────────────────────────────────────────────────────────────     │
│   CPU: ████████████████████████████████████████████ 285 ns         │
│   Alloc: 48-96 bytes (interface boxing)                            │
│                                                                     │
│   → Use reflection for SETUP, not HOT PATHS!                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Panic Risks

💥 Compiler Won't Save You!

Reflection panics at RUNTIME nếu:

go
// 1. Wrong Kind
v := reflect.ValueOf(42)
v.NumField()  // 💥 panic: reflect: call of reflect.Value.NumField on int

// 2. Invalid field access
v := reflect.ValueOf(User{})
v.FieldByName("NonExistent").String()  // 💥 panic: zero Value

// 3. Type mismatch when setting
v := reflect.ValueOf(&User{}).Elem()
v.FieldByName("Age").SetString("thirty")  // 💥 panic: string is not int

// 4. Setting unexported field
type user struct {
    name string  // lowercase = unexported
}
v := reflect.ValueOf(&user{}).Elem()
v.FieldByName("name").SetString("test")  // 💥 panic: unexported field

Always check before calling:

go
// Safe pattern
field := v.FieldByName("Name")
if field.IsValid() && field.CanSet() {
    field.SetString("new value")
}

🎮 Code Challenge: PrintFields

🧩 Architecture Challenge

Implement PrintFields(input any) that prints all field names of ANY struct:

go
// Expected behavior:
type User struct {
    ID    int64
    Name  string
    Email string
}

type Order struct {
    OrderID int64
    Total   float64
    Status  string
}

PrintFields(User{})
// Output:
// Field 0: ID (int64)
// Field 1: Name (string)
// Field 2: Email (string)

PrintFields(Order{})
// Output:
// Field 0: OrderID (int64)
// Field 1: Total (float64)
// Field 2: Status (string)

PrintFields(123)  // Should handle non-struct!
// Output:
// Error: input is not a struct (kind: int)
Solution
go
package main

import (
    "fmt"
    "reflect"
)

func PrintFields(input any) {
    t := reflect.TypeOf(input)
    v := reflect.ValueOf(input)
    
    // Handle pointer to struct
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        v = v.Elem()
    }
    
    // Validate: must be struct
    if t.Kind() != reflect.Struct {
        fmt.Printf("Error: input is not a struct (kind: %s)\n", t.Kind())
        return
    }
    
    fmt.Printf("Struct: %s\n", t.Name())
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        
        fmt.Printf("  Field %d: %s (%s) = %v\n", 
            i, 
            field.Name, 
            field.Type,
            value.Interface(),
        )
    }
}

func main() {
    type User struct {
        ID    int64
        Name  string
        Email string
    }
    
    PrintFields(User{ID: 1, Name: "Raizo", Email: "raizo@hpn.dev"})
    // Output:
    // Struct: User
    //   Field 0: ID (int64) = 1
    //   Field 1: Name (string) = Raizo
    //   Field 2: Email (string) = raizo@hpn.dev
    
    PrintFields(42)
    // Output:
    // Error: input is not a struct (kind: int)
}

Key Points:

  1. Handle both value and pointer inputs with t.Kind() == reflect.Ptr
  2. Validate Kind before calling struct-specific methods
  3. Use t.Field(i) for type info, v.Field(i) for value
  4. Interface() converts reflect.Value back to regular value

📊 Summary

ConceptKey Point
TypeOfReturns type metadata (name, fields, tags)
ValueOfReturns actual values (can read/modify)
Elem()Dereferences pointer to get underlying value
CanSet()Check before modifying (needs pointer)
Struct TagsMetadata for JSON, DB, validation mapping
Performance~100x slower than direct access
Panic RiskRuntime panics if types don't match

📌 HPN Standard: Reflection Decision

Need to handle arbitrary types?
├── Limited set of types?
│   └── Use type switch (fast, safe)
├── Go 1.18+?
│   └── Use Generics (compile-time safety)
└── Truly dynamic (JSON, ORM, DI)?
    └── Reflection OK (but only in setup/init, not hot paths)

➡️ Tiếp theo

Reflection mastered! Tiếp theo: I/O Abstractions - io.Reader, streaming, và building generic pipelines.