48 вопросов
Go в первую очередь императивный: программа описывает последовательность шагов. Декларативные конструкции есть (например, описание структур, интерфейсов, ограничений типов в дженериках), но основной код - императивный.
Базовые типы: булев bool; целые int, int8/16/32/64, uint, uint8/16/32/64, uintptr; числа с плавающей точкой float32, float64; комплексные complex64, complex128; строки string; байт и руна byte (=uint8), rune (=int32). Составные: массивы, слайсы, map, структуры, указатели, функции, каналы, интерфейсы.
int - знаковый целочисленный тип платформенного размера (32 или 64 бита). uint - беззнаковый того же размера. Для размеров и индексов в стандартной библиотеке часто используют int; uint нужен, когда важна беззнаковая арифметика (битовые маски, хеши).
int32 - 4 байта, int64 - 8 байт. Размер задан в спецификации и не зависит от платформы. Для int/uint размер равен размеру машинного слова (4 байта на 32-битной платформе, 8 на 64-битной).
В пакете math: math.MaxInt32, math.MinInt32, math.MaxInt64, math.MinInt64, math.MaxUint32, math.MaxUint64 и т.д. Для int - math.MaxInt, math.MinInt (с Go 1.17).
Размер int равен размеру машинного слова: 32 бита на 32-битной ОС, 64 бита на 64-битной. То же для uint и uintptr. Для переносимого размера используйте явно int32 или int64.
Значение по умолчанию при объявлении переменной без инициализации: 0 для чисел, false для bool, "" для string, nil для указателей, слайсов, map, каналов, интерфейсов, функций. Для структур - все поля в своих zero value.
var x T объявляет переменную типа T (или с инициализацией var x = expr). := - краткое объявление только внутри функций; тип выводится из правой части. С := можно объявить несколько переменных, при этом хотя бы одна должна быть новой.
В Go нет "ссылочных типов" в смысле C++. Слайсы, map и каналы реализованы через заголовки, указывающие на данные: при присваивании копируется заголовок, а не данные. Менять содержимое по "копии" можно - создаётся впечатление ссылочной семантики, но это не указатели на переменные.
Сравниваемы: булев, числовые, string, указатели, каналы, интерфейсы, структуры и массивы из сравниваемых полей/элементов. Не сравниваемы: слайсы, map, функции. Сравнение интерфейсов сравнивает тип и значение внутри.
Не сравниваемы: слайсы, map, функции. Их нельзя использовать как ключи map и в == / !=. Для сравнения слайсов используют reflect.DeepEqual или slices.Equal (Go 1.21+).
byte - алиас для uint8, один байт. rune - алиас для int32, кодовая точка Unicode (один символ). В строке в UTF-8 одна руна может занимать 1-4 байта. Итерация for _, r := range s даёт руны.
Строка в Go - это байты. Для ASCII символа s[i] даёт байт (тип byte). Литерал 'A' имеет тип rune, в байт: byte('A'). Строка из одного символа: string('A') или "A".
Плюсы: простой синтаксис, быстрая компиляция, статическая типизация, встроенная конкурентность (горутины, каналы), один бинарник без рантайма, хорошая стандартная библиотека. Минусы: нет дженериков в старых версиях (до 1.18), ручная работа с ошибками, меньше "синтаксического сахара" по сравнению с некоторыми языками.
Go компилируется в нативный код без виртуальной машины; нет классов и наследования, есть структуры и интерфейсы с неявной реализацией; встроенные горутины вместо потоков; явная обработка ошибок вместо исключений; сборка мусора проще и с низкой задержкой. Java - ООП, большая экосистема, дженерики с ранних версий.
Go - компилируемый, статически типизированный, быстрый. Python - интерпретируемый (часто с JIT), динамическая типизация, лаконичный синтаксис. В Go нет исключений в стиле Python, нет словарей/списков "из коробки" в том же виде; есть слайсы, map, строгая типизация и многопоточность через горутины.
Go - компилируемый. Исходный код компилируется в машинный код (или в WebAssembly). Интерпретатора стандартного нет; go run компилирует во временный файл и запускает его.
При целочисленном делении на ноль (1/0) программа паникует (runtime panic). При делении с плавающей точкой 1.0/0.0 получается +Inf по спецификации IEEE 754, паники нет.
Да. Арифметика целых - по модулю 2^n, overflow и underflow не проверяются. Например, var u uint8 = 255; u++ даёт 0. Для проверки переполнения используют пакет math/bits или явные проверки перед операциями.
Go - мультипарадигменный: императивный, процедурный, с поддержкой ООП через методы и интерфейсы (без наследования), и конкурентного программирования (CSP). Функции первого класса есть, но акцент не на чисто функциональном стиле.
make выделяет и инициализирует слайсы, map и каналы (встроенные типы, которые нельзя создать через литерал так же, как указатели). Примеры: make([]int, 0, 10), make(map[string]int), make(chan int). Для указателей и структур используют new или литералы.
Объявление вида type T = U задаёт алиас: T и U - один и тот же тип. Отличие от type T U (новый тип): алиас полностью взаимозаменяем с исходным типом, включая методы. Введено в Go 1.9.
Синтаксис T(x), где x приводится к типу T. Разрешены явные преобразования между совместимыми типами: числовые, строки и []byte/[]rune, интерфейсы и конкретные типы и т.д. Несовместимые приведения (например, int к struct) запрещены - в отличие от C, неявного приведения почти нет.
Явных sum types нет. Часто моделируют через интерфейс: несколько типов реализуют один интерфейс, а по типу в runtime различают варианты. Либо структура с дискриминатором и полями. В Go 1.18+ дженерики позволяют выражать варианты через обобщённые типы, но отдельного синтаксиса для sum types по-прежнему нет.
iota - предопределённая константа в объявлении const: её значение - порядковый индекс в текущем блоке (с нуля). Используется для перечислений: const (A = iota; B; C) даёт 0, 1, 2. Сдвиг: const (X = 1 << iota; Y; Z) - 1, 2, 4.
Нетипизированная константа (untyped) имеет "род" (kind): целое, строка, с плавающей точкой и т.д., но не конкретный тип. Её можно использовать в выражениях с разными типами без приведения. Типизированная (например, const x int64 = 1) уже имеет тип и ведёт себя как значение этого типа.
В Go switch по умолчанию не проваливается в следующий case (не нужен break). Можно писать switch без выражения (как цепочка if-else), switch true для условий. fallthrough принудительно переходит в следующий case. Типы в case должны совпадать с типом выражения (или быть интерфейсами).
Пустой идентификатор _ используется, когда значение не нужно: отбрасывание возвращаемого значения, игнорирование индекса в range, импорт пакета только ради side-effect (init). Нельзя использовать как переменную - только в присваивании или в range. Пример: for _, v := range slice.
Shadowing - когда внутренняя переменная перекрывает внешнюю с тем же именем. Например, x := 1; if true { x := 2; ... } - внутри блока x новая переменная. Часто приводит к ошибкам. В Go 1.21+ go vet может предупреждать о shadowing; аккуратно именуйте переменные или не переиспользуйте имена во вложенных блоках.
1) Переменная итерации одна на все итерации - при замыканиях и отложенном вызове все горутины могут увидеть одно и то же значение (до Go 1.22 - последнее). 2) Копирование по значению: при for _, v := range slice меняется копия, не элемент. 3) Итерация по map не детерминирована. 4) Изменение слайса в цикле может изменить количество итераций.
Нет. Конструкции a ? b : c в Go нет. Используют if/else или отдельную функцию. Часто пишут: if cond { x = a } else { x = b }. Для выбора значения по условию иногда вводят вспомогательную функцию.
unsafe.Pointer позволяет приводить указатель любого типа к указателю другого типа и обратно, обходя систему типов. Нужен для низкоуровневого кода (например, взаимодействие с C, реализация пакетов вроде reflect). Небезопасен: некорректное использование ведёт к неопределённому поведению.
reflect даёт возможность в runtime исследовать типы и значения: поля структур, методы, теги, элементы слайсов и т.д. Используют для сериализации (например, JSON), валидации, копирования по полям, dependency injection. Плата - сложность и меньшая производительность по сравнению с явным кодом.
Теги структур - строки у полей (формат ключ:значение), доступны через reflect. Используются библиотеками для JSON, XML, валидации, БД-маппинга. Пример для JSON: поле с тегом json:"name" сериализуется с ключом "name".
Директива //go:embed позволяет встраивать файлы из пакета в бинарник на этапе сборки. Пример: //go:embed static/* и переменная var fs embed.FS. Файлы доступны через embed.FS. Удобно для статики, шаблонов, конфигов без отдельного развёртывания файлов.
//go:generate command args... - директива для генерации кода. Команда go generate ./... находит такие комментарии и выполняет указанную команду в каталоге пакета. Часто используют для кодогенерации: protobuf, stringer, mock-объекты и т.д.
Build tags - комментарии в начале файла: //go:build linux или // +build linux,amd64. Файл компилируется только если условия выполняются. Используют для платформо-зависимого кода (linux, windows, race, integration) и для исключения файлов из сборки по тегам.
go build -ldflags "-X path.Var=value -s -w": -X задаёт значение переменной при линковке (часто версию, коммит); -s и -w удаляют таблицу символов и отладочную информацию, уменьшая размер бинарника.
CGO_ENABLED=0 отключает CGO. Сборка идёт без линковки с C: бинарник статический, проще кросс-компиляция, образы Docker без C-библиотек. Некоторые пакеты требуют CGO (например, часть драйверов БД); тогда в образе нужны C-компилятор и библиотеки.
Задают GOOS и GOARCH: например, GOOS=linux GOARCH=amd64 go build -o app .. Поддерживаются linux, windows, darwin, freebsd и др., архитектуры amd64, arm64, 386, arm. При CGO_ENABLED=0 не нужны кросс-компиляторы C.
new(T) выделяет память под значение типа T, обнуляет его и возвращает *T. Используется для указателей, структур. make применяется только к слайсам, map и каналам: выделяет и инициализирует внутреннюю структуру, возвращает значение типа (не указатель). Для слайса make([]T, n) создаёт слайс длины n.
fallthrough передаёт управление в следующий case без проверки его условия. В отличие от C/Java, в Go по умолчанию break после case. fallthrough должен быть последним в case. Используется редко, когда нужно явно "провалиться" в следующий блок.
Да. goto label и метка label: допустимы в пределах одной функции. Нельзя перейти в другой блок (например, внутрь или поверх объявления переменной, если она остаётся в области видимости). Часто используют для выхода из вложенных циклов или централизованной обработки ошибок.
switch выбирает по значению выражения (или по true для условий). select ждёт готовности одного из каналов: отправка/приём по каналам, возможно с default для неблокирующего выбора. В select case - только операции с каналами. Используется для мультиплексирования каналов и таймаутов.
Некоторые: //go:noinline, //go:noescape, //go:linkname, //go:nosplit, //go:embed, //go:generate, //go:build. Они управляют инлайнингом, эскейпом, линковкой, встраиванием файлов и т.д. Документация: go doc cmd/compile.
В Go 1.22 переменная итерации в for range создаётся заново на каждой итерации (как в обычном for). Раньше одна переменная переиспользовалась - замыкания и отложенные вызовы видели одно и то же значение (обычно последнее). Теперь поведение интуитивное и безопасное для горутин.
В Go 1.23 добавлена итерация по функциям-итераторам: for x := range iter, где iter - функция, возвращающая следующее значение и признак продолжения. Удобно для ленивых последовательностей и пользовательских итераторов без выделения слайса.
В Go nil может иметь тип (например, (*int)(nil), ([]int)(nil)). При сравнении интерфейса с nil: если интерфейс хранит тип и значение nil указателя, то iface == nil даёт false (typed nil). Поэтому часто проверяют ошибки через if err != nil после приведения к конкретному типу или используют errors.Is.