Иногда в Go можно встретить такой импорт:
import _ "github.com/lib/pq"У новичков он обычно вызывает справедливый вопрос: зачем импортировать пакет, если мы его нигде не используем?
Ответ простой: пакет нужен не ради функций или типов, а ради побочного эффекта при инициализации.
Что такое init()
В Go у пакета может быть специальная функция init():
func init() {
sql.Register("postgres", &Driver{})
}Она выполняется автоматически при запуске программы. Порядок примерно такой:
- Инициализируются импортированные пакеты.
- Инициализируются переменные пакета.
- Выполняется
init(). - Запускается
main().
init() нельзя вызвать руками. Go сам вызовет ее, когда пакет будет загружен.
Зачем нужен import _
Когда мы пишем:
import (
"database/sql"
_ "github.com/lib/pq"
)мы говорим компилятору: напрямую этот пакет мне не нужен, но его инициализацию выполнить надо.
Драйвер PostgreSQL регистрируется внутри database/sql, и после этого можно
писать:
db, err := sql.Open("postgres", dsn)Без blank import пакет database/sql просто не узнает, что такое драйвер
postgres. Сам database/sql дает общий интерфейс, а конкретный драйвер
добавляется через регистрацию.
Еще один пример: image/png
Похожий паттерн встречается не только в базах данных:
import (
"image"
_ "image/png"
)Пакет image/png регистрирует PNG-декодер. После этого image.Decode сможет
понимать PNG-файлы, хотя напрямую к image/png мы не обращаемся.
Когда init() уместен
init() можно использовать и в своих проектах, но важно держать правило:
side effect должен быть маленьким и ожидаемым.
Хороший вариант:
func init() {
registerDriver()
}Плохой вариант:
func init() {
connectToDatabase()
runMigrations()
startWorkers()
}Почему плохо? Потому что импорт пакета внезапно начинает делать слишком много. Ты просто подключил пакет, а он уже полез в базу, запустил горутины и сломал тесты.
Простое правило
import _ и init() очень полезны, когда нужно зарегистрировать драйвер,
формат, плагин или другую небольшую интеграцию.
Но если внутри начинается бизнес-логика, подключение к внешним сервисам или
запуск процессов, лучше сделать явный вызов из main() или из wiring-слоя.
Так код проще читать, тестировать и контролировать.
Оригинальный пост: t.me/go_for_us/38.