Giao diện
🔬 Reflection
"Reflection is powerful, but comes with costs."
Go's reflect package cho phép runtime introspection, nhưng should be used sparingly.
Go's reflect package cho phép runtime introspection, nhưng should be used sparingly.
📐 Reflection Basics
reflect.Type và reflect.Value
go
import "reflect"
func inspect(x interface{}) {
t := reflect.TypeOf(x) // Type information
v := reflect.ValueOf(x) // Value information
fmt.Printf("Type: %s\n", t.Name())
fmt.Printf("Kind: %s\n", t.Kind())
fmt.Printf("Value: %v\n", v.Interface())
}
// Usage
inspect(42)
// Type: int
// Kind: int
// Value: 42
type User struct {
Name string
Age int
}
inspect(User{Name: "Alice", Age: 30})
// Type: User
// Kind: struct
// Value: {Alice 30}Kind vs Type
go
type UserID int64
var id UserID = 42
t := reflect.TypeOf(id)
fmt.Println(t.Name()) // UserID (concrete type)
fmt.Println(t.Kind()) // int64 (underlying kind)
// Kind enum includes:
// reflect.Int, reflect.String, reflect.Struct,
// reflect.Slice, reflect.Map, reflect.Ptr, etc.🏗️ Struct Inspection
Field Iteration
go
type User struct {
ID int64 `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
isAdmin bool // unexported
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
// Handle pointer
if t.Kind() == reflect.Ptr {
t = t.Elem()
val = val.Elem()
}
if t.Kind() != reflect.Struct {
fmt.Println("Not a struct")
return
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := val.Field(i)
// Only exported fields are settable
if field.PkgPath != "" {
fmt.Printf(" %s: (unexported)\n", field.Name)
continue
}
fmt.Printf(" %s (%s): %v\n",
field.Name,
field.Type,
value.Interface(),
)
}
}
user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
inspectStruct(user)
// ID (int64): 1
// Name (string): Alice
// Email (string): alice@example.com
// isAdmin: (unexported)🏷️ Struct Tag Parsing
Reading Tags
go
type User struct {
ID int64 `json:"id" db:"user_id" validate:"required"`
Name string `json:"name" db:"user_name" validate:"required,min=2"`
Email string `json:"email" db:"email" validate:"required,email"`
}
func getDBColumns(v interface{}) []string {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var columns []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// Get "db" tag
dbTag := field.Tag.Get("db")
if dbTag != "" && dbTag != "-" {
columns = append(columns, dbTag)
}
}
return columns
}
columns := getDBColumns(User{})
// ["user_id", "user_name", "email"]Custom Tag Parser
go
type FieldInfo struct {
Name string
Column string
Required bool
Validation string
}
func parseStructTags(v interface{}) []FieldInfo {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var fields []FieldInfo
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
info := FieldInfo{
Name: field.Name,
Column: field.Tag.Get("db"),
}
// Parse validate tag
validate := field.Tag.Get("validate")
if strings.Contains(validate, "required") {
info.Required = true
}
info.Validation = validate
fields = append(fields, info)
}
return fields
}⚔️ Tradeoff: Reflection vs Alternatives
| Approach | Performance | Type Safety | Flexibility | When to Use |
|---|---|---|---|---|
| Reflection | Slow | None | High | Serialization, ORM |
| Code Gen | Fast | Compile-time | Medium | Known types |
| Generics | Fast | Compile-time | Medium | Containers |
| Interface | Fast | Compile-time | Low | Known behavior |
go
// Benchmark comparison
// reflect approach: ~500ns/op
// code gen approach: ~10ns/op (50x faster!)
// generics approach: ~15ns/op🔧 Value Modification
Setting Values (Requires Pointer)
go
func setFieldByName(v interface{}, fieldName string, newValue interface{}) error {
val := reflect.ValueOf(v)
// Must be pointer to modify
if val.Kind() != reflect.Ptr {
return errors.New("must pass pointer")
}
val = val.Elem() // Dereference
field := val.FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("field %s not found", fieldName)
}
if !field.CanSet() {
return fmt.Errorf("field %s cannot be set (unexported?)", fieldName)
}
newVal := reflect.ValueOf(newValue)
if field.Type() != newVal.Type() {
return fmt.Errorf("type mismatch: %s vs %s", field.Type(), newVal.Type())
}
field.Set(newVal)
return nil
}
// Usage
user := &User{Name: "Alice"}
setFieldByName(user, "Name", "Bob")
fmt.Println(user.Name) // Bob🎭 Dynamic Function Calls
Method Invocation
go
type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Multiply(a, b int) int { return a * b }
func callMethod(obj interface{}, method string, args ...interface{}) (interface{}, error) {
val := reflect.ValueOf(obj)
m := val.MethodByName(method)
if !m.IsValid() {
return nil, fmt.Errorf("method %s not found", method)
}
// Convert args to reflect.Value
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
// Call method
results := m.Call(in)
if len(results) == 0 {
return nil, nil
}
return results[0].Interface(), nil
}
// Usage
calc := Calculator{}
result, _ := callMethod(calc, "Add", 5, 3)
fmt.Println(result) // 8💻 Engineering Example: Generic JSON Validator
go
package validator
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
type ValidationError struct {
Field string
Message string
}
func Validate(v interface{}) []ValidationError {
var errors []ValidationError
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
t := val.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := val.Field(i)
rules := field.Tag.Get("validate")
if rules == "" {
continue
}
for _, rule := range strings.Split(rules, ",") {
if err := validateRule(field.Name, fieldVal, rule); err != nil {
errors = append(errors, *err)
}
}
}
return errors
}
func validateRule(fieldName string, val reflect.Value, rule string) *ValidationError {
parts := strings.SplitN(rule, "=", 2)
ruleName := parts[0]
ruleArg := ""
if len(parts) > 1 {
ruleArg = parts[1]
}
switch ruleName {
case "required":
if isZero(val) {
return &ValidationError{fieldName, "is required"}
}
case "min":
min, _ := strconv.Atoi(ruleArg)
switch val.Kind() {
case reflect.String:
if val.Len() < min {
return &ValidationError{fieldName, fmt.Sprintf("must be at least %d characters", min)}
}
case reflect.Int, reflect.Int64:
if val.Int() < int64(min) {
return &ValidationError{fieldName, fmt.Sprintf("must be at least %d", min)}
}
}
case "max":
max, _ := strconv.Atoi(ruleArg)
switch val.Kind() {
case reflect.String:
if val.Len() > max {
return &ValidationError{fieldName, fmt.Sprintf("must be at most %d characters", max)}
}
}
case "email":
if val.Kind() == reflect.String {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(val.String()) {
return &ValidationError{fieldName, "must be a valid email"}
}
}
}
return nil
}
func isZero(v reflect.Value) bool {
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
// Usage
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18"`
}
func main() {
req := CreateUserRequest{
Name: "A",
Email: "invalid",
Age: 15,
}
errors := Validate(req)
for _, e := range errors {
fmt.Printf("%s: %s\n", e.Field, e.Message)
}
// Name: must be at least 2 characters
// Email: must be a valid email
// Age: must be at least 18
}✅ Ship-to-Prod Checklist
Performance
- [ ] Cache reflect.Type để tránh repeated lookups
- [ ] Benchmark reflection code paths
- [ ] Consider code generation cho hot paths
- [ ] Avoid reflection trong tight loops
Safety
- [ ] Handle panics từ invalid operations
- [ ] Check CanSet() trước khi modify
- [ ] Validate Kind trước khi assume type
- [ ] Handle nil values explicitly
Code Quality
- [ ] Document why reflection is needed
- [ ] Limit scope - isolate reflection code
- [ ] Test edge cases (nil, zero values, unexported)
- [ ] Consider alternatives (generics, interfaces, code gen)
📊 Summary
| Operation | Function | Note |
|---|---|---|
| Get Type | reflect.TypeOf(x) | Returns reflect.Type |
| Get Value | reflect.ValueOf(x) | Returns reflect.Value |
| Get Kind | t.Kind() | Underlying type category |
| Get Field | t.Field(i) | Struct field by index |
| Get Tag | field.Tag.Get("json") | Tag value |
| Set Value | v.Set(newVal) | Requires pointer |
| Call Method | v.MethodByName("Foo").Call(args) | Dynamic dispatch |
➡️ Tiếp theo
Reflection nắm vững rồi! Tiếp theo: CGO & FFI - Calling C code from Go.