Quando o init() é executado em Go? Analisamos com um exemplo real
Imagine: você está escrevendo um microsserviço em Go. Em um dos pacotes, você declarou init() para carregar a configuração. Você inicia a aplicação — e de repente ela falha com nil pointer dereference. Você entra em pânico: "Por quê? A configuração deveria ter sido carregada primeiro!"
Familiar? Se sim, você não está sozinho. A pergunta "When does the init() function run?" é uma das mais populares no Stack Overflow (milhares de visualizações). Vamos entender de uma vez por todas quando e em que ordem o init() é acionado em Go, para que você não caia mais nessa armadilha.
O que é init() e para que serve?
Em Go, init() é uma função especial sem parâmetros e sem valor de retorno. Ela não é chamada explicitamente. O runtime do Go a executa automaticamente antes da execução da função main().
Para que serve?
- Inicialização de variáveis globais que exigem lógica complexa (por exemplo, leitura de arquivo).
- Registro de drivers de banco de dados ou manipuladores HTTP.
- Verificação do ambiente (variáveis de ambiente, disponibilidade de recursos).
Mas a regra principal é: você nunca deve confiar na ordem de execução do init() entre diferentes arquivos do mesmo pacote. E eis porquê.
Quando exatamente o init() é acionado? A ordem exata
O Go executa o init() seguindo regras rigorosas. Vamos detalhá-las.
Regra nº1: Um pacote — uma ordem
Dentro de um mesmo pacote, o init() é executado na ordem de declaração dos arquivos (em ordem alfabética dos nomes dos arquivos). Mas isso é um detalhe de implementação — é melhor não depender disso.
Regra nº2: Primeiro a inicialização de variáveis, depois o init()
Antes de chamar o init(), o Go inicializa todas as variáveis globais no pacote. Se uma variável depende de outra variável do mesmo pacote — a ordem de declaração delas é importante.
package config
var AppName string = "MyApp"var Version string = getVersion() // será chamado antes do init()
func init() { // Aqui AppName e Version já estão disponíveis println("Init iniciado para", AppName)}
func getVersion() string { return "1.0.0"}Regra nº3: Pacotes importados são inicializados primeiro
Esta é a regra mais importante. Se o pacote A importa o pacote B, então primeiro todos os init() no pacote B são executados, e só depois os do A. O Go constrói um grafo de dependências e executa a inicialização "de baixo para cima".
// Pacote databasepackage database
func init() { println("1. database init")}
// Pacote main (importa database)package main
import "myapp/database"
func init() { println("2. main init")}
func main() { println("3. main function")}
// Saída:// 1. database init// 2. main init// 3. main functionRegra nº4: Vários init() no mesmo arquivo — pode, mas não precisa
Em um mesmo arquivo pode haver vários init(). Eles serão executados na ordem de declaração. Mas isso dificulta a leitura do código. É melhor unificar a lógica em uma única função.
Exemplo prático: o que acontece se errar a ordem?
Vamos simular um erro típico de iniciante. Suponha que temos um pacote config, que lê as configurações de um arquivo, e um pacote app, que usa essas configurações.
// config.gopackage config
var Settings map[string]string
func init() { // Simulando a leitura de arquivo Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Config carregado")}
// app.gopackage app
import "myapp/config"
var ServerPort string
func init() { // ERRO: se config.Settings ainda não foi inici