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 Указатель в Go — это типизированный адрес значения в памяти; Go всегда передаёт аргументы по значению, поэтому указатель — единственный способ позволить функции мутировать чужое значение и избежать копии. Арифметики указателей нет (кроме `unsafe.Pointer`), есть GC, и где физически живёт значение (стек или куча) решает escape-анализ, а не наличие `&`. Выбор value vs pointer receiver влияет не только на мутацию и стоимость копий, но и на method set, а значит — на то, реализует ли тип интерфейс. ## Теория ### Что такое указатель Указатель `*T` хранит адрес значения типа `T`. Размер указателя равен машинному слову (8 байт на amd64/arm64). Над указателями определены ровно две операции: взятие адреса `&x` и разыменование `*p`. Нулевое значение указателя — `nil`. ```go x := 42 p := &x // p имеет тип *int *p = 100 // мутируем x через указатель fmt.Println(x) // 100 ``` В отличие от C, в Go нет указателей на произвольные смещения и нельзя «перепрыгнуть» в соседнюю память: указатель либо валиден и указывает на целостный объект (или элемент), либо `nil`. Это инвариант, на который опирается точный (precise) сборщик мусора — рантайм обязан знать, какие слова в памяти являются указателями. ### Передача всегда по значению В Go нет передачи по ссылке. Любой аргумент функции, любое присваивание, любой элемент при копировании структуры — копируются побитово. «Ссылочное» поведение слайсов, мап, каналов, функций и интерфейсов — это иллюзия: копируется их небольшой header/дескриптор, который внутри содержит указатель на разделяемые данные. ```go type Big struct{ data [1024]int } func mutate(b Big) { b.data[0] = 1 } // мутирует КОПИЮ, оригинал не меняется func mutateP(b *Big){ b.data[0] = 1 } // мутирует оригинал ``` При вызове `mutate(b)` копируется 8 КБ. При `mutateP(&b)` копируется 8 байт (адрес). Это и есть две главные причины использовать указатели: **мутация** и **избегание дорогих копий**. ### Стоимость копирования Копия — это `memmove` размером `sizeof(T)`. Для маленьких типов (`int`, мелкие структуры до ~машинного слова-двух) копия дешевле, чем разыменование через указатель, и дружелюбнее к кэшу/инлайнингу. Для больших структур копия дорогая. Грубый ориентир: если структура больше ~3–4 машинных слов (24–32 байт) или содержит массивы — рассматривайте указатель. Но это эвристика; реальное решение принимается профилированием и анализом семантики. ### Value vs pointer receiver ```go type Counter struct{ n int } func (c Counter) Get() int { return c.n } // value receiver: работает с копией func (c *Counter) Inc() { c.n++ } // pointer receiver: мутирует оригинал ``` Когда что выбирать: | Критерий | Value receiver | Pointer receiver | |---|---|---| | Нужно мутировать | нет | да | | Большая структура | копия дорогая → избегать | да | | Содержит `sync.Mutex`, `sync.WaitGroup` и т.п. | нельзя (копировать нельзя) | да | | Маленький immutable тип (`time.Time`, числовые обёртки) | да | можно, но не обязательно | | Семантическая «ссылочность» (slice/map внутри) | допустимо | чаще да для консистентности | **Главное правило консистентности:** не смешивайте value и pointer receivers у одного типа. Если хотя бы один метод нуждается в pointer receiver — делайте указательными все. Это убирает сюрпризы с method set и сигнализирует, что тип «должен жить за указателем». ### Как receiver влияет на method set Method set определяет, какие методы доступны у типа и, главное, реализует ли тип интерфейс. - Method set типа `T` включает все методы с **value receiver**. - Method set типа `*T` включает методы с **value receiver И pointer receiver**. ```go type Stringer interface{ String() string } type Foo struct{} func (f *Foo) String() string { return "foo" } // pointer receiver var _ Stringer = &Foo{} // OK: *Foo в method set var _ Stringer = Foo{} // ОШИБКА КОМПИЛЯЦИИ: Foo не реализует Stringer ``` Причина асимметрии: чтобы вызвать pointer-receiver метод, нужен **адресуемый** объект, у которого можно взять `&`. Из значения `T`, лежащего в интерфейсе (или возвращённого функцией), нельзя автоматически взять адрес, поэтому язык не включает pointer-методы в method set `T`. ### Автоматическое взятие адреса / разыменование Компилятор автоматически вставляет `&` и `*` при вызове методов, если операнд **адресуем**: ```go c := Counter{} c.Inc() // компилятор переписывает в (&c).Inc(), т.к. c адресуема p := &Counter{} p.Get() // компилятор переписывает в (*p).Get() ``` Но это работает только для адресуемых выражений. **Неадресуемы**: возвращаемые значения функций, элементы мапы, константы, литералы, результаты приведения типов. ```go func newCounter() Counter { return Counter{} } newCounter().Inc() // ОШИБКА: результат функции не адресуем, нельзя взять & m := map[string]Counter{"a": {}} m["a"].Inc() // ОШИБКА: элемент мапы не адресуем ``` ### Литералы &T{} и new `&T{...}` создаёт значение и сразу берёт его адрес — идиоматический способ получить `*T`. `new(T)` выделяет зануленный `T` и возвращает `*T`. `&T{}` эквивалентно `new(T)` для пустого литерала, но `&T{...}` выразительнее, когда нужна инициализация полей. ```go p1 := &Counter{n: 5} p2 := new(Counter) // *Counter, поля занулены ``` Важно: само наличие `&` не означает «аллокация в куче» — см. escape-анализ. ### Escape-анализ: стек или куча Go не различает «стековые» и «кучные» объекты на уровне синтаксиса. Компилятор выполняет **escape analysis**: если он может доказать, что значение не «убегает» за пределы фрейма функции (его адрес не сохраняется куда-то с большим временем жизни), значение размещается на стеке — даже если вы взяли `&`. Если доказать нельзя — значение «убегает» (escape) в кучу, где им управляет GC. ```go func stackAlloc() int { x := 1 p := &x // p используется локально → x остаётся на стеке return *p } func heapAlloc() *int { x := 1 return &x // адрес x уходит наружу → x escape'ит в кучу } ``` Посмотреть решения компилятора: ``` go build -gcflags='-m' ./... ``` Вывод вида `moved to heap: x` или `&x escapes to heap`. Типичные причины escape: - Возврат указателя на локальную переменную. - Сохранение указателя в поле объекта, живущего дольше. - Передача в `interface{}` (boxing) — значение часто уходит в кучу, т.к. интерфейс хранит указатель на данные. - Замыкание захватывает переменную по ссылке. - Слишком большой объект для стека / неизвестный на этапе компиляции размер. Следствие: возврат `*T` из конструктора не обязательно «дороже» возврата `T` по аллокациям — зависит от того, escape'ит ли значение в любом случае. Стек в Go растяжимый (segmented/contiguous growable stacks), поэтому стековые аллокации фактически бесплатны (просто сдвиг указателя стека) и не нагружают GC. ### Указатель на элемент массива и слайса Элементы массива и слайса **адресуемы**, на них можно взять указатель: ```go arr := [3]int{1, 2, 3} p := &arr[1] // *int на второй элемент *p = 20 // arr == [1 20 3] s := []int{1, 2, 3} q := &s[0] *q = 100 // s == [100 2 3] ``` Опасность со слайсами: при `append`, превышающем `cap`, слайс перевыделяется, и старый указатель `q` продолжает указывать на **старый** массив — мутации через него больше не видны в новом слайсе. ```go s := make([]int, 1, 1) q := &s[0] s = append(s, 2) // cap превышен → новый backing array *q = 999 // меняет СТАРЫЙ массив, s[0] не изменится ``` Элементы мапы, наоборот, **не адресуемы** (мапа может рехешироваться/переместить бакеты), поэтому `&m[k]` запрещён. ### Отсутствие арифметики указателей и unsafe.Pointer В обычном Go нельзя делать `p++`, `p + 1`, сравнивать указатели на «больше/меньше» с целью обхода памяти. Разрешено только `==`/`!=`. Это сделано ради безопасности памяти и точного GC. Исключение — пакет `unsafe`. `unsafe.Pointer` — это «универсальный» указатель, между которым и `uintptr` можно конвертировать, что и даёт фактическую арифметику: ```go import "unsafe" s := []int32{10, 20, 30} p := unsafe.Pointer(&s[0]) // сдвиг на один элемент: p2 := unsafe.Add(p, unsafe.Sizeof(s[0])) fmt.Println(*(*int32)(p2)) // 20 ``` Правила безопасности `unsafe`: - `uintptr` — это просто число, GC его **не отслеживает**. Объект, на который указывал только `uintptr`, может быть собран или перемещён. Преобразование `Pointer → uintptr → арифметика → Pointer` должно быть **в одном выражении**, без промежуточного сохранения в переменную `uintptr`. - Используйте `unsafe.Add` и `unsafe.Slice` (Go 1.17+) вместо ручной арифметики через `uintptr` — они выражают намерение и безопаснее. - Любой `unsafe` ломает гарантии портируемости и совместимости; применяйте только в горячих участках, FFI/cgo, сериализации. ## Подводные камни / gotchas ### Цикл for и взятие адреса переменной цикла (до Go 1.22) До Go 1.22 переменная цикла переиспользовалась, и `&v` во всех итерациях давал один и тот же адрес: ```go var ptrs []*int for _, v := range []int{1, 2, 3} { ptrs = append(ptrs, &v) // ДО Go 1.22: все указывают на одно и то же, итог [3 3 3] } ``` В Go 1.22+ переменная цикла создаётся заново на каждой итерации — баг исчез. На senior-собеседовании важно знать обе версии семантики и `GOEXPERIMENT`/версию модуля. ### Метод с pointer receiver на неадресуемом значении ```go m := map[string]Counter{"a": {}} m["a"].Inc() // не компилируется: элемент мапы неадресуем ``` Решение: достать копию, изменить, положить обратно; или хранить `map[string]*Counter`. ### Висячий указатель на старый backing array после append См. пример выше — типичный источник «исчезающих» мутаций. ### Копирование структуры с мьютексом ```go type S struct{ mu sync.Mutex; n int } func (s S) Bad() { s.mu.Lock() } // value receiver копирует мьютекс — go vet ругается ``` `sync`-примитивы нельзя копировать после первого использования. Всегда pointer receiver. `go vet` ловит это (`copylocks`). ### Сравнение указателей vs значений `p1 == p2` сравнивает адреса, а не содержимое. Два разных объекта с одинаковыми полями дадут `false`. Для сравнения содержимого — `reflect.DeepEqual` или сравнение значений `*p1 == *p2` (если тип сравним). ### nil pointer dereference Разыменование `nil` указателя — паника `runtime error: invalid memory address or nil pointer dereference` (SIGSEGV, перехватываемая рантаймом). ```go var p *Counter p.Inc() // паника, если метод обращается к c.n ``` Тонкость: вызов метода с pointer receiver на `nil` сам по себе НЕ паникует — паника происходит при разыменовании поля внутри. Можно даже намеренно писать методы, корректно работающие с `nil`-получателем (частый паттерн в иммутабельных деревьях): ```go func (n *Node) Size() int { if n == nil { return 0 } return 1 + n.left.Size() + n.right.Size() } ``` ### Типизированный nil в интерфейсе ```go func get() error { var p *MyErr = nil return p // интерфейс error НЕ nil! (тип *MyErr, значение nil) } get() == nil // false — классическая ловушка ``` Интерфейс равен `nil` только когда и тип, и значение nil. Возврат типизированного nil-указателя делает интерфейс «не-nil». ### &T{} не равно «куча» Распространённое заблуждение, что `&` всегда аллоцирует в куче. Решает escape-анализ. И наоборот — большое значение без `&` может уйти в кучу. ## Вопросы на собеседовании **В:** Go передаёт аргументы по значению или по ссылке? **О:** Всегда по значению. Передачи по ссылке в языке нет. Слайсы, мапы, каналы ведут себя «ссылочно» потому, что копируется их header/дескриптор, содержащий указатель на общие данные, но сам дескриптор — копия. Чтобы функция мутировала переменную вызывающего, нужно явно передать указатель. **В:** В чём разница между value и pointer receiver и как это связано с интерфейсами? **О:** Value receiver работает с копией и не мутирует оригинал; pointer — мутирует и не копирует структуру. Method set типа `T` содержит только value-методы, а `*T` — и value-, и pointer-методы. Поэтому если интерфейсный метод реализован с pointer receiver, интерфейс удовлетворяет `*T`, но не `T`. Причина — для вызова pointer-метода нужен адресуемый объект, а значение `T` в интерфейсе не адресуемо. **В:** Как Go решает, где разместить значение — на стеке или в куче? **О:** Через escape-анализ на этапе компиляции. Если компилятор доказывает, что адрес значения не переживёт фрейм функции, значение остаётся на стеке (аллокация почти бесплатна, GC не задействован). Если адрес «убегает» (возврат указателя, сохранение в долгоживущий объект, boxing в интерфейс, захват замыканием) — значение размещается в куче. Проверяется через `go build -gcflags=-m`. Наличие `&` само по себе не определяет место размещения. **В:** Почему нельзя взять адрес элемента мапы (`&m[k]`)? **О:** Мапа может перехешироваться и переместить бакеты при росте, инвалидируя любой удержанный адрес. Чтобы не создавать висячих указателей, язык делает элементы мапы неадресуемыми. Элементы слайсов и массивов адресуемы, т.к. их backing array не двигается без явного переаллока. **В:** Что произойдёт с указателем на элемент слайса после append? **О:** Если append не превысил cap, backing array тот же — указатель валиден. Если cap превышен, слайс получает новый backing array, а старый указатель указывает на прежний массив; мутации через него больше не видны в новом слайсе. Это частый источник трудноуловимых багов. **В:** Есть ли в Go арифметика указателей? Как обойти ограничение? **О:** В безопасном Go нет — разрешены только `==`/`!=`. Это нужно для memory safety и точного GC. Обход — через `unsafe.Pointer` и `unsafe.Add`/`unsafe.Slice`. Ключевой риск: `uintptr` не отслеживается GC, поэтому конвертацию `Pointer→uintptr→Pointer` нужно делать одним выражением, иначе объект может быть собран/перемещён между шагами. **В:** Паникует ли вызов pointer-receiver метода на nil-указателе? **О:** Сам вызов — нет; получатель просто равен nil. Паника возникает при разыменовании поля внутри метода. Это позволяет писать методы, корректно обрабатывающие `nil`-получатель (`if n == nil { return ... }`), что используется, например, в рекурсивных структурах. **В:** Почему функция, возвращающая `error` с типизированным nil-указателем внутри, не равна nil? **О:** Интерфейс — это пара (тип, значение). Он равен nil только если оба компонента nil. При возврате `var p *MyErr; return p` тип в интерфейсе — `*MyErr` (не nil), значение — nil. Поэтому `err != nil`. Лекарство — возвращать литеральный `nil` или проверять перед возвратом. **В:** Когда копирование значения предпочтительнее указателя? **О:** Для маленьких immutable типов: лучше кэш-локальность, нет разыменований, дружелюбнее инлайнингу, не создаётся мусор для GC, нет шеринга и гонок. Указатель оправдан при необходимости мутации, для больших структур, для типов с некопируемыми полями (`sync.Mutex`) и когда нужна идентичность объекта. ## На что копают на senior+ - **Различие «синтаксис vs размещение».** Senior чётко разделяет наличие `&`/`new` и фактическую аллокацию: место решает escape-анализ. Follow-up: «приведи случаи, когда `&x` НЕ даёт heap-аллокацию» и «когда возврат значения по копии всё равно уходит в кучу» (boxing в интерфейс, слишком большой объект). - **Связь method set ↔ интерфейсы ↔ адресуемость.** Ожидают объяснения именно через адресуемость, а не «так в спеке». Follow-up: почему `T{}.PointerMethod()` работает (переменная адресуема), а `funcReturningT().PointerMethod()` — нет. - **Стоимость в цифрах и профилирование.** Senior не гадает, а ссылается на `-gcflags=-m`, `go test -bench -benchmem`, pprof; понимает, что разыменование тоже не бесплатно (cache miss) и что для мелких типов копия может быть быстрее указателя. - **Влияние на GC.** Указатели увеличивают работу GC (scan, write barriers); структуры без указателей внутри (pointer-free) сканируются дешевле. Follow-up: как «уплощение» структур и отказ от лишних указателей снижают GC pressure; что такое write barrier. - **unsafe и инварианты рантайма.** Понимание, почему `uintptr` опасен, правил `unsafe.Pointer` из документации пакета, что moving stacks могут двигать стек (но не кучу — пока), и почему точный GC требует знания layout указателей. - **Семантика receiver как контракт API.** Senior рассуждает о консистентности (все методы одного типа — одинаковый стиль receiver), о том, что выбор pointer receiver делает тип «не value-type» и влияет на копируемость, сравнимость и потокобезопасность. - **Историческая семантика loop var (Go 1.22).** Способность назвать версию, где поведение `&v` в цикле изменилось, и объяснить мотивацию изменения и совместимость по версии модуля в `go.mod`.