Go: структуры, embedding и теги

Структура объединяет именованные поля в один тип значения. Go не имеет наследования классов: повторное использование и расширение поведения строят композицией. Вложение (embedding) поля без имени продвигает его поля и методы в selector set внешнего типа, но не делает внешний тип подтипом вложенного. Теги структур — строковые метаданные, доступные через reflection; сам язык не придаёт им значения.

Зачем это на интервью

На практике структуры моделируют запросы, доменные объекты и конфигурацию. Интервью проверяет, понимаете ли вы копирование структуры, pointer receiver, promoted methods, неоднозначные селекторы и то, что json-тег обрабатывает библиотека, а не компилятор.

Минимум для E4

  • Создавать структуры литералом по именам полей и различать value/pointer доступ к полям.
  • Объяснять, что embedding — композиция, а не наследование.
  • Называть правило: неэкспортируемое поле другого пакета нельзя задать литералом.
  • Читать распространённые теги json:"name,omitempty" и понимать, что их интерпретирует encoding/json.
package main
 
import "fmt"
 
type Audit struct{ CreatedBy string }
 
type User struct {
	ID    string `json:"id"`
	Name  string `json:"name,omitempty"`
	Audit        // embedded field; User.CreatedBy promoted
}
 
func main() {
	u := User{ID: "u1", Audit: Audit{CreatedBy: "system"}}
	fmt.Println(u.CreatedBy, u.Audit.CreatedBy)
}

В литералах экспортируемого типа из другого пакета используйте только экспортируемые поля. Позиционные литералы хрупки: добавление или перестановка полей меняет вызовы, поэтому в прикладном коде предпочтительны имена полей.

Углубление для E5/Senior

Embedding экономит запись, но может размыть API. Продвинутый метод доступен как u.Method(), однако его владелец остаётся вложенным полем; конфликт двух одинаковых имён делает сокращённый selector неоднозначным. Особенно осторожно встраивают публичные типы: будущая версия зависимости может добавить поле или метод и вызвать конфликт либо неожиданно расширить интерфейс, который удовлетворяет ваш тип.

Структура копируется при присваивании и передаче по значению. Это разумно для небольших неизменяемых данных, но копирование sync.Mutex, sync.Once, atomic-типов после первого использования некорректно. Указатель нужен не только «для скорости»: он выражает общую идентичность, мутацию или большой объект. Измерение важнее догадки о размере.

Теги — часть внешнего контракта только для конкретной библиотеки. encoding/json игнорирует неэкспортируемые поля и применяет свои правила именования, omitempty, конфликтов embedded-полей и -. Для стабильного API отделяйте transport DTO от доменной модели: так JSON-теги, nullable-поля и обратная совместимость не проникают в бизнес-инварианты.

Ключевые понятия

  • Struct type — упорядоченный набор именованных полей; идентичность типа включает имена, типы, порядок и теги.
  • Struct literal — создание значения; именованные поля устойчивее позиционных.
  • Embedded field — поле, объявленное типом или *T без отдельного имени.
  • Promotion — возможность обратиться к полю/методу вложенного поля через внешний тип при отсутствии конфликта.
  • Selector ambiguity — два пути к одинаковому имени запрещают сокращённый selector.
  • Struct tag — строка в обратных кавычках после типа поля, доступная reflect-пакету.
  • DTO — структура для границы транспорта; не обязана совпадать с доменной сущностью.

Типовые вопросы

  1. Embedding — это наследование?
    • Нет. Это поле и promotion селекторов; внешний тип не становится подтипом и не получает автоматической подстановки вместо вложенного типа.
  2. Что случится при двух embedded полях с Name?
    • x.Name неоднозначен и не скомпилируется; надо явно выбрать путь, например x.Left.Name.
  3. Кто читает json-тег?
    • Библиотека encoding/json через reflection. Компилятор не валидирует смысл строки и другая библиотека может использовать другой синтаксис.
  4. Почему нельзя копировать структуру с mutex?
    • Копия создаёт независимое состояние блокировки, что нарушает синхронизационный контракт; такие типы передают указателем и не копируют после использования.
  5. Можно ли задать неэкспортируемое поле чужой структуры?
    • Нет. Создайте значение через экспортируемый конструктор или доступный API пакета.
  6. Что значит omitempty?
    • Для JSON-поля с пустым значением библиотека пропустит поле; это может смешать «не задано» и zero value, поэтому контракт надо выбирать осознанно.

Практика

  • Спроектируйте CreateUserRequest и доменный User. Готово: JSON-теги живут только в DTO, валидация отделяет пустое имя от отсутствующего, а тесты фиксируют JSON-контракт.
  • Встройте Audit в две сущности. Готово: показаны обычный и полный selector, а тест демонстрирует разрешение либо обнаружение конфликта имён.
  • Проверьте go vet для структуры с sync.Mutex. Готово: исправлено копирование после первого использования и объяснён выбранный pointer API.

Частые ошибки и ловушки

  • Считать promoted поле собственным полем внешней структуры в JSON/API-дизайне.
  • Встраивать тип только ради короткой записи, скрывая зависимость и конфликты имён.
  • Копировать mutex, Once или атомарный объект вместе со структурой.
  • Использовать omitempty, когда клиенту важно отличить false или 0 от отсутствующего поля.
  • Делать доменную модель одновременно ORM-, JSON- и RPC-моделью без ясного контракта.

Связанные темы

Go · Основы языка Go · Указатели и семантика значений · Методы, интерфейсы и композиция

Источники