Когда и в каком порядке выполняется функция init() в Go?
Когда и в каком порядке выполняется init() в Go?
Функция init() — это специальная функция в Go, которая выполняется автоматически до функции main(). Она предназначена для инициализации состояния пакета: установки переменных, проверки конфигураций, регистрации драйверов и т.д. Важно понимать, что init() нельзя вызывать явно — она запускается рантаймом.
Порядок выполнения init()
Правила простые, но их нужно запомнить:
- Сначала — init() импортированных пакетов. Если ваш пакет main импортирует пакет
config, то init() из config выполнится перед любыми init() в main. - Внутри одного пакета — в порядке появления в исходниках. Go компилирует файлы в алфавитном порядке имён файлов (это деталь реализации, но на практике так). Поэтому init() из файла, который идёт раньше по алфавиту, выполнится первым.
- Если несколько init() в одном файле — они выполняются в порядке их объявления.
В вашем примере:
// main.go — идёт раньше по алфавиту, чем extra.go
// extra.go — идёт позже
Поэтому сначала выполняется init() из main.go, потом из extra.go. Переменная counter сначала получает значение 10, затем перезаписывается на 20. Именно поэтому в main() вы видите counter = 20.
Пример с импортированным пакетом
Допустим, у вас есть пакет config:
// config/config.go
package config
import "fmt"
var AppName string
func init() {
fmt.Println("Config init: loading settings...")
AppName = "MyApp"
}
И ваш main.go:
package main
import (
"fmt"
"yourmodule/config"
)
var counter int
func init() {
fmt.Println("Main init")
counter = config.AppName // используем данные из config
}
func main() {
fmt.Println("App:", config.AppName, "Counter:", counter)
}
Вывод будет:
Config init: loading settings...
Main init
App: MyApp Counter: MyApp
Как видите, init() из config выполнился первым, потому что пакет config импортируется. Затем — init() из main.
Важные нюансы
- init() не может принимать аргументы и возвращать значения. Это void-функция.
- Вы не можете вызвать init() вручную — компилятор выдаст ошибку.
- Если пакет импортируется, но не используется напрямую (только ради побочного эффекта init), используйте пустой импорт:
import _ "some/package". - Порядок инициализации переменных пакета — сначала инициализируются переменные верхнего уровня (в порядке их объявления), потом выполняются init().
Как проверить порядок на практике
Вы можете использовать флаг -x при сборке или просто добавить логирование в каждый init. Но помните: полагаться на порядок init() в рамках одного пакета — плохая практика. Лучше выносить логику инициализации в явные функции, которые вызываются из main().
В итоге: init() выполняется один раз при запуске программы, до main(), в строго определённом порядке: сначала импортированные пакеты (рекурсивно), затем текущий пакет (в порядке файлов по алфавиту).
<<Дополнение: как избежать зависимости от порядка init()
В больших проектах полагаться на порядок выполнения init() может быть опасно. Если вам нужно, чтобы один init() выполнялся строго после другого, лучше сделать это явно.
Вместо двух init() в разных файлах:
// Не делайте так:
func init() { setupA() }
func init() { setupB() } // B может выполниться до A
Используйте явную функцию инициализации:
// Инициализация по порядку:
func init() {
setupA()
setupB() // B гарантированно после A
}
Или, что ещё лучше, вынесите инициализацию в отдельную функцию, вызываемую из main():
func main() {
if err := initialize(); err != nil {
log.Fatal(err)
}
// ... основной код
}
Такой подход даёт полный контроль над порядком и упрощает тестирование. Используйте init() только для простых, однозначных инициализаций (например, регистрация драйвера БД), где порядок не важен.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги