37 вопросов
Продвинутые темы Go: go vet, go fmt, go:embed, сборщик мусора, рефлексия, unsafe.Pointer, variadic функции, замыкания и другие особенности языка.
go fmt? 🟢 Лёгкий
▶
go fmt (или gofmt) автоматически форматирует код: отступы табами, расстановка скобок, выравнивание. Единый стиль для всех Go-проектов.
Подробнее →Go поддерживает множественные возвращаемые значения: func div(a, b int) (int, error). Это основа обработки ошибок в Go. Можно использовать именованные возвращаемые значения.
Подробнее →[]byte в string? 🟢 Лёгкий
▶
string(b) - преобразование []byte в string. Создаёт копию данных (строки иммутабельны). Обратно: []byte(s). Эти преобразования - O(n) по памяти из-за копирования.
Подробнее →struct{} - пустая структура, занимает 0 байт. В map[string]struct{} значения не потребляют память. При миллионах ключей экономия заметна.
Подробнее →go vet? 🟡 Средний
▶
go vet - статический анализатор. Находит ошибки, которые компилятор пропускает: неправильные аргументы Printf, копирование мьютексов, недостижимый код, ошибки в тегах структур.
Подробнее →//go:embed? 🟡 Средний
▶
//go:embed (Go 1.16+) встраивает содержимое файлов в переменные при компиляции. Можно встроить строку, []byte или embed.FS. Удобно для шаблонов, статики, конфигов.
Подробнее →Go использует конкурентный трёхцветный mark-and-sweep GC. Он работает параллельно с программой, обеспечивая паузы < 1 мс. Не generational (как в Java).
Подробнее →runtime.NumGoroutine()? 🟡 Средний
▶
runtime.NumGoroutine() возвращает количество активных горутин. Полезно для мониторинга и обнаружения утечек горутин. Если число постоянно растёт - у вас утечка.
Подробнее →reflect пакет? 🟡 Средний
▶
reflect - рефлексия: можно узнать тип переменной, её поля, вызывать методы динамически. Используется в encoding/json, ORM. Медленнее прямого доступа - используйте только когда необходимо.
Подробнее →make и new? 🟡 Средний
▶
make(T, args) - создаёт и инициализирует slice, map или chan (возвращает сам тип). new(T) - выделяет память и возвращает *T с zero value. Разные задачи.
Подробнее →Интерфейс fmt.Stringer содержит метод String() string. Типы, реализующие его, автоматически используются fmt.Println для красивого вывода. Аналог toString() в Java.
Подробнее →Порядок: 1) инициализация зависимых пакетов (их init()), 2) init() пакета main, 3) main(). Все init() завершаются до начала main().
Подробнее →...T в параметре означает, что функция принимает 0+ аргументов типа T. Внутри функции это слайс []T. Примеры: fmt.Println(a ...any), append(s []T, elems ...T). Должен быть последним параметром.
Подробнее →defer fmt.Println("a"); defer fmt.Println("b")? 🟡 Средний
▶
Defer'ы выполняются в порядке LIFO (стек). Сначала зарегистрирован "a", затем "b". При выходе сначала выполняется последний ("b"), потом первый ("a").
Подробнее →Оператор ... после слайса "разворачивает" его в отдельные аргументы. f([]int{1,2,3}...) эквивалентно f(1, 2, 3). Без ... весь слайс будет одним аргументом - ошибка типов.
Подробнее →Замыкание - анонимная функция, которая "замыкает" (захватывает) переменные из окружающей области видимости. Переменные разделяются по ссылке, а не копируются. Частая ловушка: замыкание в цикле.
Подробнее →go generate? 🟡 Средний
▶
go generate ищет комментарии //go:generate command в исходниках и выполняет указанные команды. Используется для кодогенерации: stringer, mockgen, protobuf. Не запускается автоматически при go build.
Подробнее →s := "hello"; fmt.Println(s[0])? 🟡 Средний
▶
Индексация строки возвращает byte, не символ. s[0] - байт с кодом 104 (ASCII-код 'h'). fmt.Println выводит числовое значение байта. Для символа: string(s[0]) или fmt.Printf("%c", s[0]).
Подробнее →sync.Map? 🟡 Средний
▶
sync.Map - конкурентно-безопасная map, не требующая внешней блокировки. Оптимизирована для случаев, когда ключи стабильны (много чтений, мало записей).
Подробнее →go:build тег? 🟡 Средний
▶
//go:build linux - директива условной компиляции. Файл будет включён только при сборке для Linux. Можно комбинировать: //go:build linux && amd64. Заменил старый формат // +build.
Подробнее →fmt.Sprintf("%T", 42)? 🟡 Средний
▶
Глагол %T выводит тип значения. 42 - нетипизированная константа, по умолчанию int. %T полезен для отладки. Работает с любыми типами, включая кастомные.
Подробнее →sync/atomic предоставляет атомарные операции: AddInt64, LoadInt64, StoreInt64, CompareAndSwapInt64. Работают без мьютексов, на уровне процессорных инструкций. Быстрее мьютексов для простых счётчиков.
Подробнее →Method set определяет, какие интерфейсы тип реализует. У значения T - только value receiver методы. У указателя *T - и value, и pointer receiver методы. Поэтому *T может реализовать интерфейс, который T не реализует.
Подробнее →cmp.Or("", "", "default") (Go 1.22+)? 🟡 Средний
▶
cmp.Or возвращает первое ненулевое значение из аргументов. Для строк zero value - пустая строка, для чисел - 0. Аналог оператора || для значений по умолчанию в JavaScript. cmp.Or("", "", "default") = "default".
Подробнее →Graceful shutdown: 1) перехватить OS-сигнал через signal.NotifyContext; 2) вызвать server.Shutdown(ctx) - перестаёт принимать новые, дожидает текущие; 3) ctx с таймаутом ограничивает ожидание. server.Close() прерывает все соединения немедленно.
Подробнее →golang.org/x/sync/errgroup? 🟡 Средний
▶
errgroup.Group запускает горутины (g.Go), ждёт завершения (g.Wait) и возвращает первую ошибку. С errgroup.WithContext при первой ошибке отменяется context. Замена WaitGroup + ручного сбора ошибок. Стандартный инструмент для параллельных задач.
Подробнее →Go позволяет вызывать методы на nil-указателе. Метод получит nil как receiver. Паника будет только при попытке разыменовать nil внутри метода (обращение к полям).
Подробнее →Если String() вызывает fmt.Sprintf("%s", s) или fmt.Sprint(s), fmt обнаружит Stringer и снова вызовет String(). Результат - бесконечная рекурсия и stack overflow.
Подробнее →func Log(args ...any) {}; names := []string{"a","b"}; Log(names...)? 🟡 Средний
▶
[]string и []any - разные типы в Go. Нельзя развернуть []string в ...any через names... Нужно явно скопировать в []any или передать names как один аргумент: Log(names).
Подробнее →slice header: указатель на массив + len + cap = 3x8 = 24 байт. string header: указатель + len = 2x8 = 16 байт. interface: указатель на тип + указатель на данные = 2x8 = 16 байт.
Подробнее →unsafe.Pointer? 🔴 Сложный
▶
unsafe.Pointer - универсальный указатель, который можно привести к любому типу указателя. Обходит проверки типов. Используется для низкоуровневых оптимизаций. Нарушает гарантии безопасности Go.
Подробнее →panic(nil)? 🔴 Сложный
▶
panic(nil) вызывает панику. Начиная с Go 1.21, panic(nil) оборачивается в *runtime.PanicNilError, чтобы recover() мог отличить "не было паники" от "panic(nil)".
Подробнее →sync.Pool? 🔴 Сложный
▶
sync.Pool кеширует аллоцированные объекты для повторного использования. Снижает давление на GC. Объекты могут быть удалены GC в любой момент - не для постоянного хранения.
Подробнее →type MyInt int; var x MyInt = 5; var y int = 3; fmt.Println(x + y)? 🔴 Сложный
▶
type MyInt int создаёт новый тип. MyInt и int - разные типы, нельзя складывать без явного приведения: x + MyInt(y) или int(x) + y. При этом x + 3 работает, потому что 3 - нетипизированная константа.
Подробнее →func f() *int { x := 42; return &x }? 🔴 Сложный
▶
Escape analysis определяет, что &x переживёт функцию, и размещает x в куче. Без return &x переменная осталась бы на стеке. Проверить: go build -gcflags='-m'. Аллокации в куче дороже - объект обрабатывается GC.
Подробнее →//go:linkname? 🔴 Сложный
▶
//go:linkname позволяет обращаться к неэкспортированным функциям и переменным других пакетов, обходя систему видимости Go. Используется в стандартной библиотеке, но не рекомендуется в пользовательском коде - это хрупкий хак без гарантий совместимости.
Подробнее →GODEBUG? 🔴 Сложный
▶
GODEBUG управляет различными аспектами рантайма: GC (gctrace=1), сеть (netdns=go), HTTP (http2debug=1), TLS и другие. Это позволяет менять поведение программы без перекомпиляции. С Go 1.21 GODEBUG также используется для обратной совместимости.
Подробнее →