GoMiddleTechnical
Что такое функция init() и когда она вызывается?
init() вызывается автоматически при инициализации пакета — после инициализации переменных пакета и до main(). В одном пакете может быть несколько init(), они выполняются по порядку появления в коде.
Функция init() в Go
Функция init() — специальная функция в Go, которая вызывается автоматически во время инициализации пакета, до выполнения функции main(). Она не принимает аргументов и не возвращает значений. В одном пакете (и даже в одном файле) может быть несколько функций init().
Порядок инициализации
Go гарантирует следующий строгий порядок инициализации:
- Сначала инициализируются все импортированные пакеты (рекурсивно, в порядке зависимостей).
- Затем инициализируются переменные уровня пакета в порядке их объявления и зависимостей между ними.
- После этого вызываются все функции
init()файла в порядке их появления в коде. - Если в пакете несколько файлов, порядок файлов определяется алфавитным именем файла (но на это лучше не полагаться).
Пример: регистрация драйвера БД
Самый распространённый паттерн использования init() — регистрация побочного эффекта при импорте пакета:
// Файл: myapp/main.go
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq" // side-effect import: вызывает init() из pq
)
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
fmt.Println("Connected:", db.Ping() == nil)
}
Внутри пакета pq это выглядит так:
// Файл: github.com/lib/pq/conn.go
package pq
import "database/sql"
func init() {
sql.Register("postgres", &Driver{})
}
Несколько init() в одном пакете
package config
import "fmt"
var appName string
var version string
func init() {
appName = "MyService"
fmt.Println("init #1: appName set")
}
func init() {
version = "1.0.0"
fmt.Println("init #2: version set")
}
// Вывод при запуске:
// init #1: appName set
// init #2: version set
Порядок инициализации переменных пакета
package main
import "fmt"
// b зависит от a, поэтому a инициализируется первой,
// несмотря на порядок объявления
var b = a + 1
var a = 10
func init() {
fmt.Printf("a=%d, b=%d\n", a, b) // a=10, b=11
}
func main() {}
Когда использовать init()
- Регистрация плагинов, драйверов, кодеков (паттерн side-effect import).
- Инициализация глобальных переменных, которые нельзя инициализировать в одном выражении.
- Проверка инвариантов среды (например, наличие обязательных переменных окружения).
- Настройка логгеров, метрик, трейсеров на уровне пакета.
Подводные камни
- Скрытые зависимости: побочные эффекты при импорте (
_ "pkg") делают зависимости неочевидными и усложняют рефакторинг. - Порядок файлов не детерминирован: если несколько файлов пакета содержат
init(), порядок их вызова определяется компилятором и не гарантирован при переименовании файлов. - Паника в init(): если
init()паникует, программа завершается до вызоваmain(), а recover() там не поможет. - Тестирование затруднено: глобальное состояние, установленное в
init(), может загрязнять тесты между запусками — используйте TestMain для сброса. - Циклические импорты:
init()не решает проблему циклических зависимостей — Go запрещает их на уровне компилятора. - Невозможно вызвать явно:
init()нельзя вызвать вручную из кода, что исключает повторную инициализацию пакета. - Сложность отладки: при большом числе пакетов цепочка вызовов
init()становится трудно прослеживаемой — используйтеGODEBUG=inittrace=1для профилирования. - Слишком много логики: размещение сложной бизнес-логики в
init()— антипаттерн; предпочтительнее явные конструкторы вродеNew()илиSetup().
Common mistakes
- Давать ответ про init и порядок инициализации только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «init и порядок инициализации»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать init и порядок инициализации с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает init и порядок инициализации и где это используется в Go-коде.
- Связывает init и порядок инициализации с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.