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 — структура для границы транспорта; не обязана совпадать с доменной сущностью.
Типовые вопросы
- Embedding — это наследование?
- Нет. Это поле и promotion селекторов; внешний тип не становится подтипом и не получает автоматической подстановки вместо вложенного типа.
- Что случится при двух embedded полях с
Name?x.Nameнеоднозначен и не скомпилируется; надо явно выбрать путь, напримерx.Left.Name.
- Кто читает
json-тег?- Библиотека
encoding/jsonчерез reflection. Компилятор не валидирует смысл строки и другая библиотека может использовать другой синтаксис.
- Библиотека
- Почему нельзя копировать структуру с mutex?
- Копия создаёт независимое состояние блокировки, что нарушает синхронизационный контракт; такие типы передают указателем и не копируют после использования.
- Можно ли задать неэкспортируемое поле чужой структуры?
- Нет. Создайте значение через экспортируемый конструктор или доступный API пакета.
- Что значит
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 · Указатели и семантика значений · Методы, интерфейсы и композиция