Что такое функция init() в Go и каковы её особенности?

Ответ

init() — это специальная функция в Go, которая выполняется автоматически при инициализации пакета, до вызова функции main().

Ключевые особенности:

  • Автоматический вызов: init() вызывается средой выполнения Go, её нельзя вызвать явно.
  • Порядок выполнения:
    1. Сначала init() вызываются для всех импортируемых пакетов в порядке их импорта.
    2. Затем init() вызываются в текущем пакете. Если в одном файле или пакете несколько функций init(), они выполняются в порядке их лексического объявления.
  • Нет аргументов и возвращаемых значений: Функция всегда имеет сигнатуру func init().
  • Множественность: В одном пакете (и даже в одном файле) может быть несколько функций init().

Когда использовать init():

Функция init() полезна для задач, которые нужно выполнить один раз при запуске программы:

  • Инициализация глобальных переменных: Например, установка соединения с базой данных.
  • Регистрация драйверов: Классический пример — регистрация драйвера базы данных: import _ "github.com/lib/pq".
  • Проверка конфигурации: Проверка наличия необходимых переменных окружения или конфигурационных файлов.

Пример:

package main

import (
    "fmt"
    "log"
)

// Глобальная переменная, которая будет инициализирована
var config map[string]string

// Первая init-функция
func init() {
    fmt.Println("Первый init: инициализация конфигурации")
    config = make(map[string]string)
    config["version"] = "1.0"
}

// Вторая init-функция
func init() {
    fmt.Println("Второй init: проверка конфигурации")
    if config["version"] == "" {
        log.Fatal("Версия не установлена!")
    }
}

func main() {
    fmt.Println("main: приложение запущено")
    fmt.Printf("Версия из конфига: %sn", config["version"])
}

Вывод программы:

Первый init: инициализация конфигурации
Второй init: проверка конфигурации
main: приложение запущено
Версия из конфига: 1.0

Когда не стоит использовать init():

Несмотря на удобство, init() следует использовать с осторожностью:

  • Скрывает зависимости: Код в init() выполняется неявно, что затрудняет понимание и тестирование.
  • Усложняет тестирование: Тесты могут падать из-за глобального состояния, созданного в init(). Лучше использовать явные функции инициализации, которые можно контролировать в тестах.
  • Обработка ошибок: init() не может вернуть ошибку. Единственный способ сообщить о сбое — вызвать panic(), что аварийно завершит программу.

Ответ 18+ 🔞

Смотри, вот эта штука init() в Go — она, блядь, как скрытая камера в твоём коде, работает сама по себе, пока ты не видишь.

В чём, сука, соль:

  • Сама включается: Ты её не вызываешь, она просто выскакивает, как чёрт из табакерки, когда пакет инициализируется, ещё до main().
  • Порядок — всё: Сначала отрабатывают все init() из импортированных пакетов (в том порядке, как ты их импортировал), а потом уже твои. Если их несколько в одном файле — выполнятся по очереди, как написаны.
  • Ничего не принимает, ничего не отдаёт: Сигнатура у неё всегда func init(). Ни аргументов, ни возврата ошибки — ни хуя.
  • Можно натыкать дофига: В одном пакете можешь объявить несколько таких функций, и все отработают.

Когда эта хуйня полезна:

Ну, для разовых дел при старте, чтобы не засорять main():

  • Подготовить глобальные штуки: Например, подключиться к базе данных один раз и нахуй.
  • Зарегистрировать драйвер: Вот этот классический импорт с подчёркиванием import _ "github.com/lib/pq" — как раз чтобы его init() отработала и зарегистрировала драйвер в тишине.
  • Проверить, всё ли на месте: Что конфиги есть, переменные окружения заданы.

Пример, чтобы стало понятно:

package main

import (
    "fmt"
    "log"
)

// Глобальная переменная, которую будем настраивать
var config map[string]string

// Первая init-функция
func init() {
    fmt.Println("Первый init: настраиваю конфиг, блядь")
    config = make(map[string]string)
    config["version"] = "1.0"
}

// Вторая init-функция
func init() {
    fmt.Println("Второй init: проверяю, не накосячил ли я")
    if config["version"] == "" {
        log.Fatal("Ёбта, а версия-то где?!")
    }
}

func main() {
    fmt.Println("main: ну вот, теперь я запускаюсь")
    fmt.Printf("Версия из конфига: %sn", config["version"])
}

Что напечатает:

Первый init: настраиваю конфиг, блядь
Второй init: проверяю, не накосячил ли я
main: ну вот, теперь я запускаюсь
Версия из конфига: 1.0

А когда её лучше не трогать, этакую мартышку:

Да, она удобная, но с ней можно так накосячить, что потом волосы дыбом:

  • Невидимые зависимости: Код в init() выполняется тихо, и если что-то сломается, искать причину — тот ещё пиздец. Тестировать такое — мучение ебаное.
  • Сложно тестировать: Глобальное состояние, которое она создаёт, может грохнуть твои тесты самым неожиданным образом. Лучше явные функции инициализации, которые под контролем.
  • С ошибками — пизда: init() не может вернуть ошибку. Если что-то пошло не так, остаётся только panic(), что уронит всю программу к ебеням. Не очень-то изящно, да?