init e ordem de inicialização
Nesta aula, exploramos a função init() em Go, sua ordem de execução em relação a variáveis de pacote e construtores, e os cuidados necessários ao utilizá-la. Compreender a inicialização é crucial para evitar dependências ocultas e efeitos colaterais inesperados.
Em Go, a inicialização de pacotes segue regras bem definidas que garantem previsibilidade. A função init() é uma ferramenta poderosa para configurar estados iniciais, mas seu uso indevido pode levar a código frágil. Nesta aula, vamos detalhar como funciona a ordem de inicialização, desde variáveis de pacote até múltiplas funções init(), e os principais cuidados a serem tomados.
Entender o ciclo de inicialização é essencial para escrever programas robustos, especialmente em projetos com múltiplos pacotes. A linguagem Go executa inicializações de forma automática e determinística, mas o programador deve estar atento às dependências implícitas.
func init
A função init() é uma função especial que não possui parâmetros nem retorno. Ela é chamada automaticamente antes da função main() para cada pacote que é importado. Cada arquivo pode conter múltiplas funções init(), e elas são executadas na ordem em que aparecem no arquivo (e na ordem de compilação dos arquivos).
É comum usar init() para registrar drivers, inicializar variáveis globais que dependem de computação complexa, ou estabelecer conexões com bancos de dados. No entanto, seu uso deve ser moderado, pois dificulta testes e torna o fluxo de inicialização menos explícito.
package main
import "fmt"
var count int
func init() {
count = 42
fmt.Println("init() executado")
}
func main() {
fmt.Println("Valor de count:", count)
}
Ordem de execução
A ordem de inicialização em Go segue uma hierarquia clara:
- Variáveis de pacote (no nível de pacote) são inicializadas na ordem em que aparecem, considerando dependências entre elas.
- Funções
init()são executadas após todas as variáveis do pacote estarem inicializadas. - Se um pacote importa outros pacotes, os pacotes importados são inicializados primeiro (em profundidade, respeitando a ordem de importação).
- Dentro de um mesmo pacote, múltiplas funções
init()são executadas na ordem em que aparecem nos arquivos (e na ordem de compilação dos arquivos, que pode ser influenciada pela ordem de importação).
Essa ordem garante que dependências sejam resolvidas antes do uso. Por exemplo, se uma variável depende de outra, a dependência é inicializada primeiro.
package main
import "fmt"
var a = b + 1
var b = 10
func init() {
fmt.Println("init a:", a)
}
func init() {
fmt.Println("init b:", b)
}
func main() {
fmt.Println("main")
}
Nesse exemplo, b é inicializada antes de a porque a depende de b. As funções init() executam depois que todas as variáveis estão prontas.
Variáveis de pacote
Variáveis de pacote são declaradas fora de funções e podem ser inicializadas com expressões constantes ou chamadas de funções. A inicialização ocorre em tempo de execução, antes de qualquer init(). É importante notar que a ordem de inicialização entre arquivos diferentes do mesmo pacote pode ser não determinística se não houver dependências explícitas. Por isso, é recomendado evitar efeitos colaterais na inicialização de variáveis.
package config
var Debug bool
func init() {
Debug = true
}
Embora seja possível inicializar Debug diretamente com var Debug = true, usar init() permite lógica mais complexa. Contudo, prefira inicialização direta sempre que possível.
Cuidados
O uso excessivo de init() pode levar a problemas como:
- Dependências ocultas: A ordem de inicialização entre pacotes pode gerar dependências não óbvias, dificultando a manutenção.
- Testes difíceis: Funções
init()são executadas automaticamente, tornando os testes de unidade mais complexos, pois não é possível substituir facilmente o comportamento. - Efeitos colaterais globais: Modificar variáveis globais dentro de
init()pode causar comportamentos inesperados se a ordem de importação mudar. - Inicialização duplicada: Se um pacote é importado múltiplas vezes, sua inicialização ocorre apenas uma vez, mas isso pode não ser intuitivo.
Boas práticas: use init() apenas para registrar coisas (como drivers de banco de dados) ou para configurações que não podem ser feitas de outra forma. Prefira funções explícitas de inicialização que possam ser chamadas manualmente.
Referências
- Especificação da linguagem Go: Inicialização de pacotes
- Effective Go: init()
- Documentação do comando go: Compilação de pacotes
- Go Blog: The init function
- Stack Overflow: When is the init() function in Go?
Exercícios
- Explique a ordem de inicialização entre variáveis de pacote e funções init(). Dê um exemplo simples.✓ Resposta: As variáveis de pacote são inicializadas primeiro, na ordem em que aparecem (respeitando dependências). Depois, as funções init() são executadas na ordem em que aparecem nos arquivos. Exemplo:
var x = y + 1 var y = 10 func init() { println("init") } - Escreva um programa que demonstre a execução de múltiplas funções init() em um mesmo pacote, imprimindo mensagens indicando a ordem.✓ Resposta:
Saída: primeiro init, depois segundo init, depois main.package main import "fmt" func init() { fmt.Println("primeiro init") } func init() { fmt.Println("segundo init") } func main() { fmt.Println("main") } - Qual a diferença entre inicializar uma variável de pacote diretamente e usar init()? Cite uma vantagem e uma desvantagem de cada abordagem.✓ Resposta: Inicialização direta:
var x = 10é simples e clara. Vantagem: previsível e fácil de entender. Desvantagem: não permite lógica complexa. Usar init(): permite computação adicional. Vantagem: flexibilidade. Desvantagem: pode ocultar dependências e dificultar testes. - O que acontece se um pacote importa outro que também possui uma função init()? Explique a ordem de execução entre os dois pacotes.✓ Resposta: O pacote importado é inicializado primeiro (variáveis e init()) antes do pacote importador. A ordem é em profundidade: primeiro os pacotes importados pelo importado, e assim por diante.
- Reescreva o código a seguir sem usar init(), utilizando uma função explícita de inicialização chamada Init() que deve ser chamada no início de main().
package main var db string func init() { db = "conexao" } func main() { println(db) }✓ Resposta:package main var db string func Init() { db = "conexao" } func main() { Init() println(db) }