Giao diện
🔮 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/Feature | Sử dụng Reflection để... |
|---|---|
encoding/json | Map JSON keys → struct fields |
database/sql | Scan rows → struct fields |
| GORM/sqlx | Read struct tags cho column mapping |
| Validator | Check validation tags |
| Dependency Injection | Auto-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 pointerThe 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 fieldAlways 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:
- Handle both value and pointer inputs with
t.Kind() == reflect.Ptr - Validate Kind before calling struct-specific methods
- Use
t.Field(i)for type info,v.Field(i)for value Interface()converts reflect.Value back to regular value
📊 Summary
| Concept | Key Point |
|---|---|
| TypeOf | Returns type metadata (name, fields, tags) |
| ValueOf | Returns actual values (can read/modify) |
| Elem() | Dereferences pointer to get underlying value |
| CanSet() | Check before modifying (needs pointer) |
| Struct Tags | Metadata for JSON, DB, validation mapping |
| Performance | ~100x slower than direct access |
| Panic Risk | Runtime 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.