Рефлексия (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. Используйте её только когда нет ясной альтернативы.