Hard📖Теория7 min

Рефлексия (reflect)

Пакет reflect: Type, Value, Kind, инспекция структур, динамическое создание значений и производительность

Рефлексия (reflect)

Рефлексия в Go -- это способность программы исследовать и модифицировать свою собственную структуру во время выполнения. Пакет reflect позволяет узнавать типы, читать struct-теги, вызывать методы и создавать значения динамически. Это мощный, но дорогой инструмент.

reflect.TypeOf и reflect.ValueOf

Два фундаментальных вызова:

import "reflect"

var x float64 = 3.14

// TypeOf -- returns reflect.Type (describes the type)
t := reflect.TypeOf(x)
fmt.Println(t)          // float64
fmt.Println(t.Name())   // float64
fmt.Println(t.Kind())   // float64
fmt.Println(t.Size())   // 8

// ValueOf -- returns reflect.Value (holds the actual value)
v := reflect.ValueOf(x)
fmt.Println(v)             // 3.14
fmt.Println(v.Type())     // float64
fmt.Println(v.Kind())     // float64
fmt.Println(v.Float())    // 3.14
fmt.Println(v.Interface()) // 3.14 (as any)

reflect.Kind -- категория типа

Kind определяет базовую категорию типа (не путать с именованным типом):

type UserID string
type Users []User

t1 := reflect.TypeOf(UserID("abc"))
fmt.Println(t1.Name())  // UserID
fmt.Println(t1.Kind())  // string  -- Kind shows the underlying kind

t2 := reflect.TypeOf(Users{})
fmt.Println(t2.Name())  // Users
fmt.Println(t2.Kind())  // slice

// All Kind values:
// reflect.Bool
// reflect.Int, Int8, Int16, Int32, Int64
// reflect.Uint, Uint8, Uint16, Uint32, Uint64, Uintptr
// reflect.Float32, Float64
// reflect.Complex64, Complex128
// reflect.String
// reflect.Array, Slice, Map, Chan
// reflect.Func, Interface, Ptr, Struct
// reflect.UnsafePointer

Инспекция структур

Чтение полей и тегов

type User struct {
    ID        int       `json:"id" db:"user_id"`
    Name      string    `json:"name" db:"name" validate:"required,min=2"`
    Email     string    `json:"email" db:"email" validate:"required,email"`
    CreatedAt time.Time `json:"created_at" db:"created_at"`
    password  string    // unexported -- not visible via reflection from other packages
}

func inspectStruct(v any) {
    t := reflect.TypeOf(v)

    // If pointer, get the element type
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }

    if t.Kind() != reflect.Struct {
        fmt.Println("Not a struct")
        return
    }

    fmt.Printf("Struct: %s (%d fields)\n", t.Name(), t.NumField())

    for i := range t.NumField() {
        field := t.Field(i)
        fmt.Printf("  %s: %s", field.Name, field.Type)

        // Read struct tags
        if jsonTag := field.Tag.Get("json"); jsonTag != "" {
            fmt.Printf(" (json:%q)", jsonTag)
        }
        if dbTag := field.Tag.Get("db"); dbTag != "" {
            fmt.Printf(" (db:%q)", dbTag)
        }
        if validateTag := field.Tag.Get("validate"); validateTag != "" {
            fmt.Printf(" (validate:%q)", validateTag)
        }

        // Check if exported
        if !field.IsExported() {
            fmt.Print(" [unexported]")
        }

        fmt.Println()
    }
}

// Output:
// Struct: User (5 fields)
//   ID: int (json:"id") (db:"user_id")
//   Name: string (json:"name") (db:"name") (validate:"required,min=2")
//   Email: string (json:"email") (db:"email") (validate:"required,email")
//   CreatedAt: time.Time (json:"created_at") (db:"created_at")
//   password: string [unexported]

Чтение значений полей

func readFields(v any) {
    val := reflect.ValueOf(v)

    // Dereference pointer
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }

    t := val.Type()

    for i := range val.NumField() {
        field := t.Field(i)
        value := val.Field(i)

        if !field.IsExported() {
            fmt.Printf("  %s: [unexported]\n", field.Name)
            continue
        }

        fmt.Printf("  %s = %v\n", field.Name, value.Interface())
    }
}

user := User{ID: 1, Name: "Alice", Email: "[email protected]"}
readFields(user)
// ID = 1
// Name = Alice
// Email = [email protected]
// CreatedAt = 0001-01-01 00:00:00 +0000 UTC
// password: [unexported]

Динамический вызов методов

type Calculator struct{}

func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Mul(a, b int) int { return a * b }

func callMethod(obj any, methodName string, args ...any) ([]any, error) {
    v := reflect.ValueOf(obj)

    method := v.MethodByName(methodName)
    if !method.IsValid() {
        return nil, fmt.Errorf("method %s not found", methodName)
    }

    // Convert args to reflect.Value
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }

    // Call the method
    results := method.Call(in)

    // Convert results back
    out := make([]any, len(results))
    for i, r := range results {
        out[i] = r.Interface()
    }

    return out, nil
}

// Usage
calc := Calculator{}
results, err := callMethod(calc, "Add", 10, 20)
fmt.Println(results[0]) // 30

Создание значений динамически

reflect.New -- создание через указатель

// Create new *User
t := reflect.TypeOf(User{})
ptr := reflect.New(t)       // Like new(User) -- returns *User as reflect.Value
user := ptr.Elem()          // Dereference to get User

// Set fields
user.FieldByName("ID").SetInt(42)
user.FieldByName("Name").SetString("Bob")
user.FieldByName("Email").SetString("[email protected]")

// Get back as interface{}
result := ptr.Interface().(*User)
fmt.Printf("%+v\n", *result)
// {ID:42 Name:Bob Email:[email protected] CreatedAt:0001-01-01 00:00:00 +0000 UTC}

reflect.MakeSlice и reflect.MakeMap

// Create []string dynamically
sliceType := reflect.SliceOf(reflect.TypeOf(""))
slice := reflect.MakeSlice(sliceType, 0, 10)

// Append values
slice = reflect.Append(slice, reflect.ValueOf("hello"))
slice = reflect.Append(slice, reflect.ValueOf("world"))

result := slice.Interface().([]string)
fmt.Println(result) // [hello world]

// Create map[string]int dynamically
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
m := reflect.MakeMapWithSize(mapType, 10)

m.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
m.SetMapIndex(reflect.ValueOf("two"), reflect.ValueOf(2))

resultMap := m.Interface().(map[string]int)
fmt.Println(resultMap) // map[one:1 two:2]

Изменение значений: v.Set() и v.Elem()

Для изменения значений нужен указатель на переменную:

x := 42

// This will NOT work:
v := reflect.ValueOf(x)
// v.SetInt(100) // PANIC: reflect.Value.SetInt using unaddressable value

// This works -- pass a pointer:
v = reflect.ValueOf(&x)
v.Elem().SetInt(100)
fmt.Println(x) // 100

// Check if settable
v = reflect.ValueOf(&x).Elem()
fmt.Println(v.CanSet()) // true

v2 := reflect.ValueOf(x)
fmt.Println(v2.CanSet()) // false

Изменение полей структуры

type Config struct {
    Host string
    Port int
}

func setField(obj any, field string, value any) error {
    v := reflect.ValueOf(obj)
    if v.Kind() != reflect.Ptr {
        return errors.New("must pass pointer")
    }

    v = v.Elem()
    f := v.FieldByName(field)
    if !f.IsValid() {
        return fmt.Errorf("field %s not found", field)
    }
    if !f.CanSet() {
        return fmt.Errorf("field %s is not settable", field)
    }

    val := reflect.ValueOf(value)
    if f.Type() != val.Type() {
        return fmt.Errorf("type mismatch: field is %s, value is %s", f.Type(), val.Type())
    }

    f.Set(val)
    return nil
}

cfg := &Config{Host: "localhost", Port: 8080}
setField(cfg, "Port", 9090)
fmt.Println(cfg.Port) // 9090

Производительность рефлексии

Рефлексия значительно медленнее прямого доступа:

type Point struct {
    X, Y float64
}

// Direct access: ~0.3 ns/op
func BenchmarkDirect(b *testing.B) {
    p := Point{X: 1, Y: 2}
    for b.Loop() {
        _ = p.X + p.Y
    }
}

// Reflection: ~30-100 ns/op (100x slower!)
func BenchmarkReflect(b *testing.B) {
    p := Point{X: 1, Y: 2}
    v := reflect.ValueOf(p)
    for b.Loop() {
        _ = v.Field(0).Float() + v.Field(1).Float()
    }
}

// Method call via reflection: ~200 ns/op
func BenchmarkReflectMethodCall(b *testing.B) {
    calc := Calculator{}
    v := reflect.ValueOf(calc)
    method := v.MethodByName("Add")
    args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
    for b.Loop() {
        method.Call(args)
    }
}

Типичное замедление: 10-100x в зависимости от операции.

Практические применения

1. Простой валидатор

func validate(v any) []string {
    var errs []string

    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }

    t := val.Type()

    for i := range t.NumField() {
        field := t.Field(i)
        value := val.Field(i)
        tag := field.Tag.Get("validate")

        if tag == "" {
            continue
        }

        rules := strings.Split(tag, ",")
        for _, rule := range rules {
            switch {
            case rule == "required":
                if value.IsZero() {
                    errs = append(errs, fmt.Sprintf("%s is required", field.Name))
                }
            case strings.HasPrefix(rule, "min="):
                minStr := strings.TrimPrefix(rule, "min=")
                min, _ := strconv.Atoi(minStr)
                if value.Kind() == reflect.String && value.Len() < min {
                    errs = append(errs, fmt.Sprintf("%s must be at least %d characters", field.Name, min))
                }
            }
        }
    }

    return errs
}

2. Struct -> Map конвертер

func structToMap(v any) map[string]any {
    result := make(map[string]any)

    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }

    t := val.Type()

    for i := range t.NumField() {
        field := t.Field(i)
        if !field.IsExported() {
            continue
        }

        // Use json tag name if available
        name := field.Name
        if jsonTag := field.Tag.Get("json"); jsonTag != "" {
            parts := strings.Split(jsonTag, ",")
            if parts[0] != "" && parts[0] != "-" {
                name = parts[0]
            }
        }

        result[name] = val.Field(i).Interface()
    }

    return result
}

3. Generic deep equal

// reflect.DeepEqual -- built-in deep comparison
a := map[string][]int{"x": {1, 2, 3}}
b := map[string][]int{"x": {1, 2, 3}}

fmt.Println(reflect.DeepEqual(a, b)) // true
fmt.Println(a == b)                   // Compile error! Maps aren't comparable

// Useful in tests
if !reflect.DeepEqual(got, want) {
    t.Errorf("got %v, want %v", got, want)
}

Три закона рефлексии (Rob Pike)

Статья Роба Пайка "The Laws of Reflection" формулирует три закона:

Закон 1: Рефлексия идёт от interface к reflection object

var x float64 = 3.14
v := reflect.ValueOf(x) // interface{} -> reflect.Value

Закон 2: Рефлексия идёт от reflection object к interface

v := reflect.ValueOf(3.14)
x := v.Interface().(float64) // reflect.Value -> interface{} -> float64

Закон 3: Для изменения reflection object нужна settability

var x float64 = 3.14

v := reflect.ValueOf(x)
// v.SetFloat(2.0) -- PANIC: not settable

v = reflect.ValueOf(&x).Elem()
v.SetFloat(2.0) // OK: settable through pointer

Когда использовать и когда избегать

Используйте рефлексию для:

  • Сериализации/десериализации (JSON, XML, ORM маппинг)
  • DI контейнеров (автоматический wiring зависимостей)
  • Валидаторов на основе struct-тегов
  • Тестирования (deep equal, comparing structs)
  • Фреймворков и библиотек

Избегайте рефлексии когда:

  • Можно использовать дженерики (Go 1.18+)
  • Можно использовать интерфейсы
  • Нужна высокая производительность (hot path)
  • Код становится нечитаемым
  • Можно использовать кодогенерацию (go generate)

Правило Go: "Clear is better than clever." Рефлексия -- это clever. Используйте её только когда нет ясной альтернативы.

Проверь себя

🧪

В чём разница между reflect.TypeOf и reflect.ValueOf?

🧪

Почему reflect.Value.SetInt() может вызвать панику?

🧪

Насколько рефлексия медленнее прямого доступа к полям?

🧪

Когда дженерики (Go 1.18+) предпочтительнее рефлексии?