Init functions e ordem de inicialização
1. O que são Init Functions?
Em Go, a função init() é uma função especial que executa automaticamente antes da função main(). Ela não aceita parâmetros, não retorna valores e não pode ser chamada explicitamente pelo programador. O compilador Go identifica e executa todas as funções init() definidas em um programa.
package main
import "fmt"
func init() {
fmt.Println("Executando init()")
}
func main() {
fmt.Println("Executando main()")
}
Diferenças entre init() e main():
- init() pode existir em qualquer pacote; main() apenas no pacote main
- Podem existir múltiplos init() em um mesmo pacote; apenas um main() por programa
- init() não pode ser chamado diretamente; main() é chamado pelo runtime
2. Ordem de Inicialização em um Único Pacote
Dentro de um único pacote, a ordem segue regras claras:
- Primeiro, variáveis no nível do pacote são inicializadas
- Depois, as funções
init()são executadas
Exemplo com variáveis e init:
package main
import "fmt"
var x = func() int {
fmt.Println("Inicializando x")
return 10
}()
func init() {
fmt.Println("Primeiro init")
}
func init() {
fmt.Println("Segundo init")
}
func main() {
fmt.Println("Valor de x:", x)
}
Saída:
Inicializando x
Primeiro init
Segundo init
Valor de x: 10
Ordem entre múltiplos arquivos: Quando um pacote possui múltiplos arquivos, as variáveis e init() são processados em ordem alfabética dos nomes dos arquivos.
// arquivo: a_init.go
package main
func init() {
fmt.Println("init de a_init.go")
}
// arquivo: b_init.go
package main
func init() {
fmt.Println("init de b_init.go")
}
Saída:
init de a_init.go
init de b_init.go
3. Ordem de Inicialização entre Pacotes Dependentes
O Go adota uma abordagem bottom-up: pacotes importados são inicializados antes dos pacotes que os importam.
// pacote a/a.go
package a
import "fmt"
func init() {
fmt.Println("Pacote A: init")
}
// pacote b/b.go
package b
import "fmt"
func init() {
fmt.Println("Pacote B: init")
}
// pacote main
package main
import (
"fmt"
"meuprojeto/a"
"meuprojeto/b"
)
func init() {
fmt.Println("Pacote main: init")
}
func main() {
fmt.Println("Executando main")
}
Saída:
Pacote A: init
Pacote B: init
Pacote main: init
Executando main
Regras adicionais:
- Se pacotes no mesmo nível de dependência, a ordem é alfabética
- Se A importa B, B é inicializado antes de A
- Dependências transitivas seguem a mesma regra recursivamente
4. Múltiplas Init Functions no Mesmo Pacote
É possível ter múltiplos init() no mesmo arquivo ou em arquivos diferentes do mesmo pacote.
package config
import "fmt"
var settings map[string]string
func init() {
fmt.Println("Config init 1")
settings = make(map[string]string)
}
func init() {
fmt.Println("Config init 2")
settings["timeout"] = "30s"
settings["retries"] = "3"
}
Boas práticas: Evite criar dependências entre múltiplos init() do mesmo pacote. A ordem dentro do mesmo arquivo segue a declaração, mas entre arquivos depende da ordem alfabética, o que pode ser frágil.
5. Init Functions e Ciclos de Importação
O compilador Go não permite ciclos de importação. Se o pacote A importa B e B importa A, o compilador gera erro.
// pacote a/a.go
package a
import "meuprojeto/b"
func init() {
b.Do()
}
// pacote b/b.go
package b
import "meuprojeto/a"
func init() {
a.Do()
}
Erro: import cycle not allowed
Solução: Extrair a funcionalidade comum para um terceiro pacote ou usar interfaces.
// pacote common/common.go
package common
type Service interface {
Do()
}
// pacote a/a.go
package a
import "meuprojeto/common"
func UseService(s common.Service) {
s.Do()
}
// pacote b/b.go
package b
import "meuprojeto/common"
type BService struct{}
func (b BService) Do() {
// implementação
}
6. Casos de Uso Comuns
Registro de drivers de banco de dados:
package main
import (
"database/sql"
_ "github.com/lib/pq" // init() registra o driver PostgreSQL
)
func main() {
db, err := sql.Open("postgres", "dsn")
// ...
}
Inicialização de configurações:
package config
var AppConfig struct {
Port int
Debug bool
}
func init() {
AppConfig.Port = 8080
AppConfig.Debug = true
}
População de mapas globais:
package handlers
var routeMap = map[string]func(){}
func init() {
routeMap["/api/users"] = handleUsers
routeMap["/api/products"] = handleProducts
}
7. Boas Práticas e Armadilhas
Evite lógica complexa em init():
// Ruim: lógica complexa em init
func init() {
resp, err := http.Get("https://api.exemplo.com/config")
if err != nil {
panic(err)
}
// processamento demorado
}
// Bom: função explícita de inicialização
func InitConfig() error {
resp, err := http.Get("https://api.exemplo.com/config")
if err != nil {
return err
}
return processConfig(resp)
}
Cuidado com concorrência: Não use goroutines dentro de init().
Testabilidade: Código em init() dificulta testes unitários. Prefira funções explícitas:
// Em vez de init(), use:
func Initialize() error {
// lógica de inicialização
return nil
}
func TestSomething(t *testing.T) {
if err := Initialize(); err != nil {
t.Fatal(err)
}
// teste
}
8. Comparação com Table-Driven Tests
init() pode preparar dados para testes, mas tem limitações:
var testCases []struct {
input int
expected int
}
func init() {
testCases = []struct {
input int
expected int
}{
{1, 2},
{3, 4},
}
}
func TestWithInit(t *testing.T) {
for _, tc := range testCases {
result := process(tc.input)
if result != tc.expected {
t.Errorf("esperado %d, obtido %d", tc.expected, result)
}
}
}
Alternativa com inicialização explícita:
func getTestCases() []struct {
input int
expected int
} {
return []struct {
input int
expected int
}{
{1, 2},
{3, 4},
}
}
func TestWithExplicit(t *testing.T) {
for _, tc := range getTestCases() {
result := process(tc.input)
if result != tc.expected {
t.Errorf("esperado %d, obtido %d", tc.expected, result)
}
}
}
A inicialização explícita é preferível por ser mais testável e previsível.
Referências
- Effective Go - Initialization — Documentação oficial sobre funções init() e inicialização em Go
- Go Specification - Package initialization — Especificação detalhada da ordem de inicialização de pacotes
- Go Blog - Defer, Panic, and Recover — Artigo do blog oficial que discute init() no contexto de tratamento de erros
- Go by Example - init functions — Exemplos práticos e didáticos sobre funções init() em Go
- Dave Cheney - The init function — Artigo técnico aprofundado sobre boas práticas e armadilhas do uso de init()
- Stack Overflow - How to test init functions in Go — Discussão sobre estratégias para testar código que depende de init()
- Go Wiki - CommonMistakes — Lista de erros comuns em Go, incluindo problemas com init() e ciclos de importação