init e ordem de inicialização
Esta aula explica a função especial init em Go, sua ordem de execução, a inicialização de variáveis de pacote e os cuidados necessários ao usar esse recurso. Com exemplos práticos, o aluno aprenderá como o runtime do Go gerencia a inicialização de pacotes e como evitar armadilhas comuns.
Em Go, a função init é uma função especial que pode ser definida em qualquer pacote para executar lógica de inicialização antes da função main. Ela é chamada automaticamente pelo runtime e não pode ser invocada explicitamente. Compreender a ordem de inicialização é fundamental para escrever programas robustos, especialmente quando há dependências entre pacotes.
Nesta aula, exploraremos como declarar funções init, a sequência exata de execução, como variáveis de pacote são inicializadas e os cuidados necessários para evitar efeitos colaterais indesejados. Ao final, você será capaz de utilizar init de forma consciente e segura.
func init
A função init é uma função sem parâmetros e sem valor de retorno. Ela pode ser definida em qualquer arquivo .go dentro de um pacote, e é possível ter múltiplas funções init no mesmo pacote ou até mesmo no mesmo arquivo. Cada uma será executada na ordem em que aparece no código (dentro de um arquivo) e na ordem de compilação dos arquivos (por nome).
Exemplo simples:
package main
import "fmt"
func init() {
fmt.Println("init executado")
}
func main() {
fmt.Println("main executado")
}
Ao executar, a saída será:
init executado
main executado
Isso mostra que init roda antes de main. Se houver múltiplas funções init, elas executam na ordem de declaração:
package main
import "fmt"
func init() {
fmt.Println("primeiro init")
}
func init() {
fmt.Println("segundo init")
}
func main() {
fmt.Println("main")
}
Saída:
primeiro init
segundo init
main
Ordem de execução
A ordem de inicialização em Go segue regras precisas. Primeiro, todas as variáveis de pacote são inicializadas (na ordem de declaração, respeitando dependências). Em seguida, as funções init do pacote são executadas. Esse processo ocorre para cada pacote, começando pelos pacotes importados (em profundidade, seguindo a ordem de importação).
Considere dois pacotes: pkg1 e pkg2, onde main importa ambos. A ordem será:
- Inicialização de variáveis de pacote em
pkg1(se houver) e execução de seusinit. - Inicialização de variáveis de pacote em
pkg2e execução de seusinit. - Inicialização de variáveis de pacote em
maine execução de seusinit. - Execução de
main().
Exemplo prático:
// pkg1.go
package pkg1
import "fmt"
var X int = 10
func init() {
fmt.Println("pkg1 init, X =", X)
}
// pkg2.go
package pkg2
import "fmt"
var Y int = 20
func init() {
fmt.Println("pkg2 init, Y =", Y)
}
// main.go
package main
import (
"fmt"
"meupacote/pkg1"
"meupacote/pkg2"
)
var Z int = 30
func init() {
fmt.Println("main init, Z =", Z)
}
func main() {
fmt.Println("main executando")
}
Saída esperada:
pkg1 init, X = 10
pkg2 init, Y = 20
main init, Z = 30
main executando
Note que a ordem dos imports no main.go determina a ordem de inicialização dos pacotes importados. Se a importação for "pkg2" antes de "pkg1", a ordem muda.
Variáveis de pacote
Variáveis de pacote são inicializadas antes das funções init. Elas podem ser inicializadas com expressões que chamam funções, desde que não haja dependências circulares. O compilador analisa as dependências e resolve a ordem de inicialização automaticamente.
Exemplo:
package main
import "fmt"
var (
a = c + 1 // depende de c
b = 2
c = b + 1 // depende de b
)
func init() {
fmt.Println("a =", a, "b =", b, "c =", c)
}
func main() {}
Saída:
a = 4 b = 2 c = 3
O compilador ordenou a inicialização: primeiro b, depois c (que depende de b), depois a (que depende de c). Se houver dependência cíclica, o compilador gera erro.
É importante notar que a inicialização de variáveis de pacote ocorre antes de qualquer init, e as funções init podem usar essas variáveis já inicializadas.
Cuidados
Embora init seja útil, seu uso excessivo pode tornar o código menos legível e difícil de testar. Como init é executado automaticamente, ele pode causar efeitos colaterais inesperados, especialmente em bibliotecas. Evite colocar lógica complexa em init; prefira inicialização explícita na função main ou em funções separadas.
Outro cuidado: a ordem de inicialização entre pacotes pode ser surpreendente se houver dependências indiretas. Sempre verifique a árvore de importações. Além disso, init não pode ser chamado diretamente, então não é possível reexecutar a lógica de inicialização. Se precisar de reconfiguração, use funções exportadas.
Boas práticas:
- Use
initapenas para configurações simples, como registro de drivers ou definição de variáveis globais que dependem de estado externo (ex.: variáveis de ambiente). - Evite depender da ordem de execução entre diferentes pacotes; se necessário, reestruture o código para ser explícito.
- Para testes, lembre-se que
inité executado antes de cada teste em um pacote. Isso pode poluir o estado global entre testes; considere usarTestMainpara controlar a inicialização.
Referências
- Effective Go - init
- Go Specification - Package initialization
- Go Blog - init order
- Testing package - TestMain
- Go Wiki - Common Mistakes - init
Exercícios
Escreva um programa com duas funções
initque imprimam mensagens diferentes. Execute e observe a ordem.✓ Resposta:package main import "fmt" func init() { fmt.Println("primeiro init") } func init() { fmt.Println("segundo init") } func main() { fmt.Println("main") }Saída:
primeiro init segundo init mainCrie dois pacotes:
aeb. O pacoteatem uma variávelX = 1e uminitque imprime o valor deX. O pacotebtem uma variávelY = X + 1e uminitque imprimeY. Emmain, importe ambos e veja a ordem de execução. O que acontece?✓ Resposta:Isso causa um erro de compilação porque
bdepende dea.X, masanão importabe vice-versa? Na verdade,bprecisa importarapara usarX. A ordem de inicialização será: primeiro inicializaa(variável X e init), depoisb(variável Y e init). Exemplo:// a/a.go package a import "fmt" var X = 1 func init() { fmt.Println("a init, X =", X) } // b/b.go package b import ( "fmt" "meupacote/a" ) var Y = a.X + 1 func init() { fmt.Println("b init, Y =", Y) } // main.go package main import ( _ "meupacote/a" _ "meupacote/b" ) func main() {}Saída:
a init, X = 1 b init, Y = 2Declare três variáveis de pacote com dependências:
a = b + 1,b = c + 1,c = 1. Imprima os valores em uminit. Qual a ordem de inicialização?✓ Resposta:package main import "fmt" var ( c = 1 b = c + 1 a = b + 1 ) func init() { fmt.Println("a =", a, "b =", b, "c =", c) } func main() {}Saída:
a = 3 b = 2 c = 1. A ordem de inicialização éc,b,a.Explique por que é arriscado usar
initpara configurar conexões de banco de dados em uma biblioteca. Como você evitaria esse problema?✓ Resposta:Usar
initpara conectar ao banco torna a inicialização implícita e difícil de controlar. Se a conexão falhar, o programa pode não iniciar. Além disso, em testes, oinitserá executado antes de cada teste, possivelmente conectando a um banco real. A melhor prática é fornecer uma função explícita de inicialização (ex.:Connect()) que o usuário chama quando necessário, e usarinitapenas para registrar drivers ou configurações que não dependem de estado externo.Crie um programa que use
initpara ler uma variável de ambiente e armazená-la em uma variável global. Se a variável não estiver definida, use um valor padrão. Escreva o código.✓ Resposta:package main import ( "fmt" "os" ) var ConfigValue string func init() { if v := os.Getenv("MY_CONFIG"); v != "" { ConfigValue = v } else { ConfigValue = "default" } } func main() { fmt.Println("ConfigValue =", ConfigValue) }