Перейти к основному содержимому

Рефлексия в Go

Разработчику Архитектору

См. также: Справочник по Go · Важные интерфейсы · Особенности языка.

Что такое рефлексия

Рефлексия — анализ и изменение типов и значений во время выполнения через пакет reflect. В Go типы известны компилятору; рефлексия нужна, когда тип заранее не фиксирован (универсальный декодер, ORM, валидатор по тегам).

Два базовых типа:

  • reflect.Type — описание типа (поля структуры, kind);
  • reflect.Value — конкретное значение, возможность чтения/записи (если addressable).
v := reflect.ValueOf(&user).Elem() // указатель → значение для записи в поля
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
tag := f.Tag.Get("json")
_ = tag
}

Type assertion и reflect

Если известен конечный набор типов — предпочтительнее утверждение типа:

switch x := v.(type) {
case int:
fmt.Println(x * 2)
case string:
fmt.Println(len(x))
default:
fmt.Println("неизвестный тип")
}

reflect оправдан при произвольных any / map[string]any / динамическом JSON без целевой структуры. С Go 1.18+ многие «обобщённые» задачи решают дженериками без рефлексии.


Теги структур

Теги — метаданные полей для инструментов и библиотек:

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

Компилятор теги игнорирует. encoding/json читает json через рефлексию (или codegen в обход). Правила:

  • ключ omitempty — пропуск нулевых значений в JSON;
  • имя в теге переопределяет имя поля в выводе.

Кастомная сериализация без рефлексии на горячем пути:

func (u User) MarshalJSON() ([]byte, error) { /* ... */ }

Изменение значений

Запись в поле через рефлексию возможна только если:

  • передан указатель в ValueOf;
  • вызван Elem();
  • поле экспортируемое (с заглавной буквы).

Иначе CanSet() вернёт false. Это защита инкапсуляции на уровне runtime.


Создание значений

t := reflect.TypeOf(MyStruct{})
ptr := reflect.New(t) // *MyStruct
slice := reflect.MakeSlice(reflect.SliceOf(t), 0, 10)

Используют в фреймворках DI, мапперах, тестовых фабриках.


Стоимость и риски

МинусСледствие
Медленнее прямого кодаНе в hot path
Нет проверки на этапе компиляцииОшибки в рантайме
Сложность отладкиПаники при неверном Kind

Рекомендации сообщества: «рефлексия — последний инструмент». Предпочитать:

  • явные типы и интерфейсы;
  • кодогенерацию (go generate, stringer, sqlc, ent);
  • дженерики для контейнеров и алгоритмов.

Где рефлексия неизбежна

  • encoding/json, xml, yaml по умолчанию;
  • валидация validate по тегам;
  • часть ORM (GORM) — с оговоркой по производительности;
  • плагины и registry по имени типа.

Для API и микросервисов чаще выбирают явные DTO и генерацию кода (работа с БД).


Проверка интерфейса на этапе компиляции

Без рефлексии убеждаются, что тип реализует интерфейс:

var _ io.Reader = (*MyType)(nil)

Если методов не хватает — ошибка компиляции, а не сюрприз в runtime.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).