Senior Go Interview Prep - Core Go: https://go.vbloher.org/docs/01-core-go/ - Механика defer в Go: https://go.vbloher.org/docs/01-core-go/defer/ - Встраивание структур и интерфейсов (Embedding): https://go.vbloher.org/docs/01-core-go/embedding/ - Ошибки в Go: error, wrapping, errors.Is/As/Join: https://go.vbloher.org/docs/01-core-go/errors/ - Дженерики в Go (1.18+): https://go.vbloher.org/docs/01-core-go/generics/ - Интерфейсы в Go: https://go.vbloher.org/docs/01-core-go/interfaces/ - Устройство map в Go: https://go.vbloher.org/docs/01-core-go/maps/ - panic / recover: механика, раскрутка стека и runtime-паники: https://go.vbloher.org/docs/01-core-go/panic-recover/ - Указатели в Go: https://go.vbloher.org/docs/01-core-go/pointers/ - Рефлексия в Go (reflect): https://go.vbloher.org/docs/01-core-go/reflection/ - Внутреннее устройство слайсов в Go: https://go.vbloher.org/docs/01-core-go/slices/ - Строки, руны и байты в Go: https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/ - Система типов Go: defined types, alignment, memory layout: https://go.vbloher.org/docs/01-core-go/type-system/ - Concurrency: https://go.vbloher.org/docs/02-concurrency/ - sync/atomic: https://go.vbloher.org/docs/02-concurrency/atomic/ - Буферизованные vs небуферизованные каналы: https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/ - Канал vs Mutex: когда что выбрать: https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/ - Каналы: устройство hchan: https://go.vbloher.org/docs/02-concurrency/channels/ - Утечки горутин, дедлоки, livelock, starvation: https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/ - sync.Cond: https://go.vbloher.org/docs/02-concurrency/cond/ - context: https://go.vbloher.org/docs/02-concurrency/context/ - Горутины: жизненный цикл, стоимость, стек: https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/ - sync.Mutex и sync.RWMutex: https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/ - sync.Once: https://go.vbloher.org/docs/02-concurrency/once/ - Паттерны конкурентности: https://go.vbloher.org/docs/02-concurrency/patterns/ - Race Detector (гонки данных и -race): https://go.vbloher.org/docs/02-concurrency/race-detector/ - Планировщик GMP: https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/ - select: https://go.vbloher.org/docs/02-concurrency/select/ - sync.WaitGroup: https://go.vbloher.org/docs/02-concurrency/waitgroup/ - Runtime и память: https://go.vbloher.org/docs/03-runtime-memory/ - Паттерны аллокаций и снижение давления на GC: https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/ - Escape Analysis: когда переменная убегает в кучу: https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/ - Сборщик мусора Go: concurrent tri-color mark-sweep: https://go.vbloher.org/docs/03-runtime-memory/gc/ - Тюнинг GC: GOGC и GOMEMLIMIT: https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/ - GOMAXPROCS: параллелизм планировщика и проблема контейнеров: https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/ - Утечки горутин (goroutine leaks): https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/ - Утечки памяти в Go (несмотря на GC): https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/ - Модель памяти Go (Go Memory Model): happens-before и синхронизация: https://go.vbloher.org/docs/03-runtime-memory/memory-model/ - pprof: профилирование CPU, памяти и блокировок в Go: https://go.vbloher.org/docs/03-runtime-memory/pprof/ - Execution Tracer и runtime/trace: тайминги вместо агрегатов: https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/ - Стек vs Куча: где живут данные в Go: https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/ - Тестирование: https://go.vbloher.org/docs/04-testing/ - testify, assert/require и golden files: https://go.vbloher.org/docs/04-testing/assertions-testify/ - Бенчмарки в Go: https://go.vbloher.org/docs/04-testing/benchmarks/ - Покрытие, -race и флаки-тесты: https://go.vbloher.org/docs/04-testing/coverage-race/ - Нативный fuzzing в Go (1.18+): https://go.vbloher.org/docs/04-testing/fuzzing/ - Интеграционные тесты, testcontainers-go, TestMain: https://go.vbloher.org/docs/04-testing/integration-testcontainers/ - Моки, стабы и тестируемость: https://go.vbloher.org/docs/04-testing/mocks/ - Table-driven тесты, subtests и параллельность: https://go.vbloher.org/docs/04-testing/table-driven/ - Backend: https://go.vbloher.org/docs/05-backend/ - Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты: https://go.vbloher.org/docs/05-backend/auth-authz/ - Graceful Shutdown HTTP/gRPC сервера в Go: https://go.vbloher.org/docs/05-backend/graceful-shutdown/ - gRPC: типы RPC, интерсепторы, контекст, метаданные, error model: https://go.vbloher.org/docs/05-backend/grpc/ - JWT (JSON Web Token): https://go.vbloher.org/docs/05-backend/jwt/ - Middleware-паттерн в Go: https://go.vbloher.org/docs/05-backend/middleware/ - net/http: Server, Handler, ServeMux, таймауты, Client и контекст: https://go.vbloher.org/docs/05-backend/net-http/ - OAuth2: роли, grant types, OIDC, токены и типовые ошибки: https://go.vbloher.org/docs/05-backend/oauth2/ - OpenAPI/Swagger, code generation, contract-first vs code-first, валидация: https://go.vbloher.org/docs/05-backend/openapi/ - Protocol Buffers: схемы, wire format, эволюция и совместимость: https://go.vbloher.org/docs/05-backend/protobuf/ - REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки: https://go.vbloher.org/docs/05-backend/rest/ - Сети и протоколы: https://go.vbloher.org/docs/06-networking/ - Пулы соединений: http.Transport, БД, утечки: https://go.vbloher.org/docs/06-networking/connection-pooling/ - DNS: записи, резолвинг, кэширование, DNS в Go: https://go.vbloher.org/docs/06-networking/dns/ - Версии HTTP: 1.1, 2, 3: https://go.vbloher.org/docs/06-networking/http-versions/ - TCP/IP: модель, транспорт и что важно бэкендеру: https://go.vbloher.org/docs/06-networking/tcp-ip/ - TLS: handshake, сертификаты, mTLS, производительность: https://go.vbloher.org/docs/06-networking/tls/ - UDP и надёжность поверх UDP: https://go.vbloher.org/docs/06-networking/udp/ - WebSocket: upgrade, фреймы, масштабирование: https://go.vbloher.org/docs/06-networking/websocket/ - Базы данных: https://go.vbloher.org/docs/07-databases/ - Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer: https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/ - Взаимоблокировки (Deadlocks) в PostgreSQL: https://go.vbloher.org/docs/07-databases/deadlocks/ - Индексы в PostgreSQL: https://go.vbloher.org/docs/07-databases/indexes/ - Уровни изоляции транзакций в PostgreSQL: https://go.vbloher.org/docs/07-databases/isolation-levels/ - MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat: https://go.vbloher.org/docs/07-databases/mvcc/ - Обзор NoSQL и Redis: https://go.vbloher.org/docs/07-databases/nosql-redis/ - Партиционирование таблиц в PostgreSQL: https://go.vbloher.org/docs/07-databases/partitioning/ - Архитектура PostgreSQL: https://go.vbloher.org/docs/07-databases/postgresql-architecture/ - Планирование и оптимизация запросов в PostgreSQL: https://go.vbloher.org/docs/07-databases/query-planning/ - Репликация в PostgreSQL: https://go.vbloher.org/docs/07-databases/replication/ - Шардирование (горизонтальное масштабирование): https://go.vbloher.org/docs/07-databases/sharding/ - Транзакции в PostgreSQL и Go (database/sql, pgx): https://go.vbloher.org/docs/07-databases/transactions/ - Распределённые системы: https://go.vbloher.org/docs/08-distributed-systems/ - CAP теорема: https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/ - Circuit Breaker: https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/ - Консенсус и Raft: репликация состояния в присутствии отказов: https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/ - Модели согласованности: https://go.vbloher.org/docs/08-distributed-systems/consistency/ - Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once: https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/ - Eventual Consistency: https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/ - Идемпотентность в распределённых системах: https://go.vbloher.org/docs/08-distributed-systems/idempotency/ - Apache Kafka: https://go.vbloher.org/docs/08-distributed-systems/kafka/ - Transactional Outbox: https://go.vbloher.org/docs/08-distributed-systems/outbox/ - RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka: https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/ - Ретраи: backoff, jitter, budgets и идемпотентность: https://go.vbloher.org/docs/08-distributed-systems/retries/ - Saga Pattern: https://go.vbloher.org/docs/08-distributed-systems/saga/ - Observability: https://go.vbloher.org/docs/09-observability/ - Grafana: https://go.vbloher.org/docs/09-observability/grafana/ - Метрики: RED, USE, Golden Signals: https://go.vbloher.org/docs/09-observability/metrics/ - OpenTelemetry: https://go.vbloher.org/docs/09-observability/opentelemetry/ - Prometheus: https://go.vbloher.org/docs/09-observability/prometheus/ - SLI / SLO / SLA: https://go.vbloher.org/docs/09-observability/slo-sli/ - Структурированное логирование (slog): https://go.vbloher.org/docs/09-observability/structured-logging/ - Distributed Tracing: https://go.vbloher.org/docs/09-observability/tracing/ - System Design: https://go.vbloher.org/docs/10-system-design/ - Analytics Pipeline: https://go.vbloher.org/docs/10-system-design/analytics-pipeline/ - Chat System: https://go.vbloher.org/docs/10-system-design/chat/ - Фреймворк System Design интервью: https://go.vbloher.org/docs/10-system-design/framework/ - Notification Service: https://go.vbloher.org/docs/10-system-design/notification-service/ - Order Service: https://go.vbloher.org/docs/10-system-design/order-service/ - Payment Service: https://go.vbloher.org/docs/10-system-design/payment-service/ - Rate Limiter: https://go.vbloher.org/docs/10-system-design/rate-limiter/ - URL Shortener: https://go.vbloher.org/docs/10-system-design/url-shortener/ - DevOps: https://go.vbloher.org/docs/11-devops/ - CI/CD: пайплайны, стадии, стратегии деплоя: https://go.vbloher.org/docs/11-devops/cicd/ - Облака (AWS / GCP) для бэкендера: https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/ - Docker для Go-разработчика: https://go.vbloher.org/docs/11-devops/docker/ - GitHub Actions и GitLab CI: https://go.vbloher.org/docs/11-devops/github-gitlab-ci/ - Kubernetes для Go-разработчика: https://go.vbloher.org/docs/11-devops/kubernetes/ - Terraform / Infrastructure as Code: https://go.vbloher.org/docs/11-devops/terraform/ - Алгоритмы: https://go.vbloher.org/docs/12-algorithms/ - Типовые алгоритмические задачи и паттерны: https://go.vbloher.org/docs/12-algorithms/common-problems/ - Асимптотическая сложность (Big-O): https://go.vbloher.org/docs/12-algorithms/complexity/ - Структуры данных в Go: https://go.vbloher.org/docs/12-algorithms/data-structures/ - Специфика live-coding на Go: https://go.vbloher.org/docs/12-algorithms/go-specifics/ - Behavioral: https://go.vbloher.org/docs/13-behavioral/ - Конфликты, разногласия и работа со стейкхолдерами: https://go.vbloher.org/docs/13-behavioral/conflicts/ - Как проходит senior-интервью: этапы, оценка, оффер: https://go.vbloher.org/docs/13-behavioral/interview-flow/ - Лидерство и менторство: https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/ - Типовые поведенческие вопросы для Senior: https://go.vbloher.org/docs/13-behavioral/senior-questions/ > Модуль: Core Go · Уровень: Senior ## TL;DR Встраивание (embedding) — это включение анонимного поля типа (структуры, интерфейса, указателя на структуру или любого именованного типа) в другую структуру. Go продвигает (promote) поля и методы встроенного типа во внешний, давая синтаксический сахар, но это **композиция, а не наследование**: внешний тип не является подтипом внутреннего, динамической диспетчеризации между ними нет, а конфликты имён разрешаются по глубине вложенности. Понимание того, что под капотом это просто доступ к вложенному полю по сгенерированному компилятором пути, снимает почти все вопросы о method promotion, shadowing и неоднозначности. ## Теория ### Что такое встраивание Встраивание — это объявление поля без имени, только с типом. Имя поля при этом совпадает с *именем типа* (без пакета). ```go type Animal struct { Name string } func (a Animal) Speak() string { return a.Name + " makes a sound" } type Dog struct { Animal // встроенное поле, имя поля == "Animal" Breed string } d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Husky"} fmt.Println(d.Name) // продвинутое поле: на самом деле d.Animal.Name fmt.Println(d.Speak()) // продвинутый метод: на самом деле d.Animal.Speak() ``` Ключевая идея: `d.Name` и `d.Speak()` — это чистый синтаксический сахар. Компилятор разворачивает их в `d.Animal.Name` и `d.Animal.Speak()`. Никакого vtable-наследования, никакого `super`, никакого изменения раскладки внутреннего типа. ### Что можно встраивать - Именованную структуру: `Animal` - Указатель на структуру: `*Animal` - Интерфейс: `io.Reader` - Любой именованный тип: `type MyInt int`, `sync.Mutex`, и т.д. - Дженерик-инстанс: `Slice[int]` (встроить можно конкретную инстанциацию) Нельзя: - Встроить безымянный (литеральный) тип: `struct{ X int }` напрямую — нет. - Встроить тот же тип дважды на одном уровне (конфликт имени поля). - Встроить тип-параметр напрямую (до недавних версий — ограничение; именованный тип на основе параметра тоже нельзя как встроенный). ### Имя встроенного поля Имя поля — это **неполное** имя типа: для `*Animal` имя поля `Animal`, для `sync.Mutex` имя поля `Mutex`, для `bytes.Buffer` — `Buffer`. ```go type T struct { *bytes.Buffer } var t T t.Buffer = &bytes.Buffer{} // обращение по имени типа без пакета и без звёздочки ``` ### Method promotion (продвижение методов) под капотом Спецификация Go (раздел про method sets) формулирует это так: - Если `S` содержит встроенное поле `T`, то method set `S` (и `*S`) включает продвинутые методы с приёмником `T`. - Method set `*S` дополнительно включает методы с приёмником `*T`. - Если встроено `*T`, то method set и `S`, и `*S` включает методы с приёмниками как `T`, так и `*T`. Под капотом компилятор для каждого продвинутого метода генерирует **wrapper-метод** (промежуточную функцию), которая вычисляет правильный приёмник (через смещение поля) и вызывает оригинальный метод. То есть `Dog.Speak` существует как реальный метод во множестве методов `Dog`, и в нём тело сводится к `return d.Animal.Speak()`. Таблица для значения vs указателя на встроенное: | Встроено | Method set `S` | Method set `*S` | |----------|----------------|------------------| | `T` (методы на `T`) | да | да | | `T` (методы на `*T`) | **нет** | да | | `*T` (методы на `T`) | да | да | | `*T` (методы на `*T`) | да | да | Практический вывод: если у вас встроено значение `T`, а методы интерфейса определены на `*T`, то `S` (значение) **не** удовлетворит интерфейс — только `*S`. Это частый источник ошибки "does not implement". ```go type Counter struct{ n int } func (c *Counter) Inc() { c.n++ } // приёмник-указатель type Service struct { Counter // встроено по значению } var s Service s.Inc() // ОК, т.к. s адресуема -> (&s.Counter).Inc() type Incer interface{ Inc() } var _ Incer = Service{} // ОШИБКА: Service не реализует Incer (метод на *Counter) var _ Incer = &Service{} // ОК ``` Почему `s.Inc()` работает, а `Service{}` как значение интерфейса — нет? Потому что `s` — адресуемая переменная, компилятор берёт адрес автоматически. Но method set типа `Service` (значение), используемый при проверке удовлетворения интерфейса, **не** включает методы с приёмником `*Counter`. ### Встраивание интерфейса в структуру Можно встроить интерфейс. Тогда структура получает все методы интерфейса (формально), а реализация делегируется хранящемуся значению. ```go type Logger struct { io.Writer // встроенный интерфейс prefix string } l := Logger{Writer: os.Stdout, prefix: "[app] "} l.Write([]byte("hi")) // делегируется в os.Stdout.Write ``` Идиомы и нюансы: - **Декоратор / частичная замена**: встраиваете интерфейс, переопределяете один-два метода, остальные делегируются. Так делают обёртки над `http.ResponseWriter`, `sort.Interface` и т.п. - **Опасность nil**: если встроенное интерфейсное поле `nil`, любой непереопределённый вызов метода даст panic (nil interface dereference). Структура «формально» реализует интерфейс, но падает в рантайме. - **Встраивание интерфейса в интерфейс**: классическая композиция — `io.ReadWriter = interface{ Reader; Writer }`. Это объединение method set'ов. С Go 1.18 интерфейсы могут содержать ещё и элементы-множества типов (type sets), но встраивание именованных интерфейсов работает как объединение методов и ограничений. ### Композиция vs наследование Go сознательно не имеет наследования. Сравнение: | Свойство | Наследование (ООП) | Встраивание (Go) | |----------|--------------------|------------------| | Отношение | "is-a", подтипизация | "has-a" + сахар доступа | | Полиморфизм базового метода | да (виртуальные методы, override меняет поведение базового вызова) | **нет** | | Доступ снаружи | через сам объект | через объект ИЛИ по имени поля | | Множественное | часто запрещено/ромб | несколько встроенных полей разрешено | | Связь | сильная | слабее, но всё равно есть связность | **Критический момент — отсутствие виртуальной диспетчеризации.** Если метод встроенного типа `Base` вызывает другой метод `Base`, и вы «переопределили» этот метод во внешнем типе, базовый метод **всё равно вызовет свою версию**, а не вашу. Нет позднего связывания через внешний тип. ```go type Base struct{} func (Base) Name() string { return "base" } func (b Base) Greet() string { return "Hi, " + b.Name() } // вызывает Base.Name, ВСЕГДА type Derived struct{ Base } func (Derived) Name() string { return "derived" } // "переопределение" d := Derived{} fmt.Println(d.Name()) // "derived" fmt.Println(d.Greet()) // "Hi, base" <-- НЕ "derived"! ``` В языке с виртуальными методами `Greet` напечатал бы "Hi, derived". В Go приёмник `Greet` — это `Base`, и он ничего не знает о `Derived`. Это фундаментальное отличие, и senior обязан его знать. ### Конфликты имён, глубина и неоднозначность Правило разрешения (shallow wins / самая малая глубина): - Поле или метод на **меньшей глубине вложенности** скрывает (shadow) то же имя на большей глубине. - Если на **одной и той же** наименьшей глубине есть два одинаковых имени — это **неоднозначность**: выбор не делается. Обращение `x.Name` без явного пути не компилируется (если используется) или метод просто отсутствует в method set (если речь о методе и нужно удовлетворить интерфейс). ```go type A struct{ X int } type B struct{ X int } type C struct { A B } var c C // c.X // ОШИБКА: ambiguous selector c.X c.A.X = 1 // ОК, явный путь c.B.X = 2 // ОК ``` Важно: неоднозначность — **ленивая**. Само объявление `C` валидно. Ошибка возникает только если вы реально пишете `c.X`. Если никогда не обращаться к `X` неквалифицированно — кода скомпилируется. Глубина важнее «количества»: ```go type Inner struct{ V int } type Mid struct{ Inner } // V на глубине 2 относительно Outer type Outer struct { Mid V int // V на глубине 0 (собственное поле) } // Outer.V -> собственное поле (глубина 0 побеждает), Mid.Inner.V затенено ``` ### Shadowing (перекрытие методов) «Переопределение» во внешнем типе — это shadowing: вы объявляете метод с тем же именем на внешнем типе на глубине 0, он скрывает продвинутый метод. Доступ к скрытому остаётся через имя поля. ```go type Writer struct{} func (Writer) Write(p []byte) (int, error) { /*...*/ return len(p), nil } type CountingWriter struct { Writer n int } func (c *CountingWriter) Write(p []byte) (int, error) { // shadow c.n += len(p) return c.Writer.Write(p) // явный вызов скрытого метода через имя поля } ``` `c.Write(...)` вызовет внешний (свой) метод. `c.Writer.Write(...)` — внутренний. Это и есть аналог `super.method()`, но всегда явный. ### Доступ к встроенному через имя типа - Поле: `outer.Inner.Field` - Метод: `outer.Inner.Method()` - Для встроенного указателя: `outer.Inner` уже указатель, обращение то же `outer.Inner.Field`. - Композиция имени: для `pkg.Type` имя поля = `Type` (без `pkg`). Для `*pkg.Type` тоже `Type`. Это позволяет «достать» внутренний экземпляр целиком: например, чтобы передать встроенный `sync.Mutex`... (на самом деле его передавать не нужно — мьютекс копировать нельзя, но доступ есть). ### Указатель vs значение при встраивании | | Встроено `T` (значение) | Встроено `*T` (указатель) | |---|---|---| | Раскладка памяти | `T` лежит inline внутри внешней структуры | внешняя структура хранит указатель (8 байт), `T` где-то в куче/стеке | | nil-риск | нет (всегда есть значение) | да: nil-указатель -> panic при продвинутом вызове | | Совместное использование | копируется вместе с внешним | разделяемое: копии внешнего делят один `*T` | | Method set значения внешнего | методы на `T` | методы на `T` **и** `*T` | | Инициализация | автоматически нулевое значение | нужно явно выделить, иначе nil | | Копирование внешнего | копирует встроенное значение | копирует указатель (мелкая копия) | ```go type WithVal struct{ sync.Mutex } // мьютекс inline, копировать WithVal НЕЛЬЗЯ (vet ругается) type WithPtr struct{ *sync.Mutex } // указатель; копии делят один мьютекс ``` ## Подводные камни / gotchas ### 1. Нет виртуальной диспетчеризации (см. Base/Derived выше) Самая частая ловушка для пришедших из Java/C++. Метод базового типа никогда не «увидит» переопределение в производном. ### 2. Method set значения vs указателя Встроили `T` по значению, а методы интерфейса на `*T` — значение внешнего типа не реализует интерфейс. Лечится встраиванием `*T` или использованием `&S{}`. ### 3. Копирование встроенного `sync.Mutex` / `sync.WaitGroup` Встраивание `sync.Mutex` по значению заодно делает внешнюю структуру некопируемой логически. Передача по значению копирует мьютекс -> разные блокировки, гонки. `go vet` (copylocks) ловит это. ```go type Cache struct { sync.Mutex data map[string]int } func process(c Cache) {} // BUG: копия мьютекса; vet: "passes lock by value" ``` ### 4. Неоднозначность не падает на объявлении Структура с конфликтующими полями компилируется. Ошибка только в точке неквалифицированного использования. Легко не заметить, пока кто-то не обратится к полю. ### 5. nil встроенного интерфейса ```go type S struct{ io.Reader } var s S s.Read(nil) // panic: nil pointer dereference (Reader == nil) ``` Структура формально удовлетворяет `io.Reader`, проверка компилируется, рантайм падает. ### 6. Продвижение тегов и сериализации Встраивание влияет на JSON/encoding. Встроенная **именованная** структура по умолчанию «всплывает» (её поля сериализуются как поля внешнего объекта). Но если у встроенного поля есть JSON-тег или это не структура — поведение меняется. ```go type Base struct{ ID int `json:"id"` } type User struct { Base // поля Base всплывают: {"id":1,"name":...} Name string `json:"name"` } // vs type User2 struct { Base `json:"base"` // теперь вложенный объект: {"base":{"id":1},...} } ``` Конфликт имён полей при JSON решается аналогично селекторам: меньшая глубина побеждает; одинаковая глубина -> поле пропускается. ### 7. Встраивание `interface` ради «реализации» без всех методов Приём (часто в тестах / forward-compat): встроить большой интерфейс, реализовать только нужное. ```go type fakeConn struct{ net.Conn } // net.Conn == nil func (f fakeConn) Read(b []byte) (int, error) { return 0, io.EOF } ``` Удобно, но любой невызванный-но-вызванный метод -> panic. Опасно при росте интерфейса. ### 8. Встраивание одинакового имени поля `struct{ A; *A }` — конфликт: оба дают имя поля `A`. Не компилируется. ### 9. Экспортируемость продвигается «как есть» Неэкспортируемый метод/поле встроенного типа из другого пакета не продвигается за пределы пакета (доступен только внутри пакета, где определён). Продвижение не «повышает» видимость. ## Вопросы на собеседовании **В:** Является ли встраивание наследованием? Чем отличается? **О:** Нет. Это композиция плюс синтаксический сахар на доступ к полям и методам вложенного типа. Главные отличия: (1) нет отношения подтипа — `Dog` не является `Animal` и не присваивается переменной типа `Animal`; (2) нет виртуальной диспетчеризации — метод встроенного типа вызывает только свои методы, а не «переопределённые» во внешнем; (3) доступ к встроенному возможен явно по имени поля (`d.Animal`). Под капотом `d.Method()` разворачивается в `d.Animal.Method()` через сгенерированный wrapper. **В:** Что напечатает `Greet` в примере, где `Base.Greet` зовёт `Name()`, а `Derived` переопределяет `Name`? **О:** Версию `Base.Name`, потому что приёмник `Greet` — это `Base`, и он не знает о существовании `Derived`. Нет позднего связывания. Чтобы получить полиморфизм, нужно явно передавать поведение, например через интерфейс-поле или через функциональное поле, а не через встраивание. **В:** Встроили `T` по значению, методы определены на `*T`. Реализует ли значение внешней структуры интерфейс с этими методами? **О:** Нет. Method set значения внешнего типа включает только методы с приёмником `T` из встроенного значения, но не методы с приёмником `*T`. Их получит только method set указателя на внешний тип. Поэтому `var _ I = S{}` не скомпилируется, а `var _ I = &S{}` — да. При этом прямой вызов `s.Method()` на адресуемой переменной работает за счёт автоматического взятия адреса — это отдельный механизм, не относящийся к удовлетворению интерфейса. **В:** Как разрешается конфликт, если два встроенных типа имеют поле с одинаковым именем? **О:** По глубине вложенности: меньшая глубина побеждает (shadowing). Если одинаковое имя на одной наименьшей глубине — это неоднозначность (ambiguous selector). Объявление структуры остаётся валидным; ошибка компиляции возникает только при неквалифицированном обращении. Разрешается явным путём: `c.A.X` / `c.B.X`. **В:** В чём разница между встраиванием `T` и `*T`? **О:** `T` лежит inline в раскладке внешней структуры (нет лишнего указателя, нет nil-риска, копируется вместе с внешним). `*T` — это указатель: внешняя структура хранит 8 байт, требует явной инициализации (иначе nil и panic при продвинутом вызове), копии внешнего разделяют один и тот же `*T`. Кроме того, встраивание `*T` даёт значению внешнего типа доступ и к методам на `T`, и на `*T`. **В:** Зачем встраивать интерфейс в структуру? **О:** Паттерн декоратора/обёртки: встраиваем интерфейс, переопределяем нужные методы, остальное делегируется хранимому значению. Так пишут обёртки над `http.ResponseWriter`, прокси, инструментирование. Также удобно для тестовых заглушек, реализующих лишь часть большого интерфейса. Риск: если встроенное поле nil, непереопределённые методы вызовут panic, хотя тип формально удовлетворяет интерфейсу. **В:** Что происходит при сериализации встроенной структуры в JSON? **О:** По умолчанию поля встроенной именованной структуры «всплывают» на уровень внешнего объекта (как будто это собственные поля). Если на встроенном поле стоит JSON-тег, оно становится вложенным объектом под этим именем. Конфликты имён полей разрешаются по тем же правилам глубины, что и селекторы: меньшая глубина побеждает, равная — поле выпадает из вывода. Это поведение пакета `encoding/json`, опирающееся на правила Go о продвижении полей. **В:** Как вызвать «переопределённый» (скрытый) метод встроенного типа? **О:** Через явное имя встроенного поля: `c.Writer.Write(...)`. Это Go-аналог `super.method()`, но всегда явный — неявного `super` нет. **В:** Можно ли встроить два типа с одинаковым неполным именем, например `pkg1.Conn` и `pkg2.Conn`? **О:** Нет, оба дадут имя поля `Conn` на одном уровне — конфликт имени поля, не компилируется. Нужно дать одному явное имя (тогда это уже обычное именованное поле, без продвижения). ## На что копают на senior+ - **Wrapper-методы и стоимость**: senior понимает, что продвинутые методы — это реально существующие методы (компилятор генерирует переходники), а не магия рантайма. Может рассуждать об инлайнинге этих переходников и о том, что глубокое встраивание не добавляет рантайм-оверхеда сверх обычного доступа к полю по смещению. - **Method set и приёмники под микроскопом**: follow-up «почему `s.Inc()` компилируется, но `var _ I = s` нет» — нужно различать механизм автоматического взятия адреса для адресуемых значений и формальное определение method set типа. Это разные правила спецификации. - **Отсутствие полиморфизма базового типа как design constraint**: senior предложит правильное решение задачи «template method pattern» в Go — не встраивание, а передача зависимостей через интерфейс/функциональные поля (dependency injection), потому что встраивание не даёт виртуального вызова. - **copylocks и встраивание sync-примитивов**: ожидается знание, что встраивание `sync.Mutex` делает тип некопируемым, что это ловит `go vet`, и почему `*sync.Mutex` или вынесение в неэкспортируемое поле — варианты. - **Диамантовая проблема в Go**: follow-up про ромбовидное встраивание (`D{B; C}` где оба встраивают `A`). Senior объяснит, что Go не «сливает» базу: будет два независимых экземпляра `A`, доступ к продвинутым членам `A` станет неоднозначным, и это решается явными путями — Go перекладывает разрешение на программиста вместо C++-подобных правил виртуального наследования. - **Встраивание дженериков**: с Go 1.18+ можно встраивать конкретную инстанциацию (`List[int]`), но не сам тип-параметр; знание этих границ — признак того, что человек следит за эволюцией языка. - **JSON/ORM-эффекты продвижения**: реальные баги в продакшене из-за всплытия полей встроенных моделей (например, базовая модель с `ID`/`CreatedAt`), коллизий тегов и неожиданной (де)сериализации — senior связывает правила продвижения полей с поведением рефлексии в библиотеках.