O que é Init em Go?
A função init() é uma func especial em Go que é executada automaticamente durante a inicialização de um package, antes da função main(). Diferente de qualquer outra função na linguagem, init() não recebe parâmetros, não retorna valores e não pode ser chamada diretamente — ela é invocada automaticamente pelo runtime do Go.
A init() serve para executar configurações que precisam acontecer antes do programa começar a rodar: registrar drivers de banco de dados, inicializar variáveis globais complexas, validar configurações de ambiente ou configurar logging. É um mecanismo poderoso, mas que deve ser usado com parcimônia para manter o código testável e previsível.
Toda linguagem de programação precisa de um mecanismo de inicialização, e Go optou por uma abordagem explícita com init() em vez de construtores estáticos ou atributos de classe. Essa decisão mantém a transparência — você sempre sabe exatamente onde a inicialização acontece.
Ordem de execução
A sequência de inicialização em Go segue uma ordem determinística e bem definida:
- Importações recursivas — todos os packages importados são inicializados primeiro, recursivamente
- Variáveis de pacote — declarações
varem nível de package são avaliadas em ordem de dependência - Funções
init()— todas as funçõesinit()do package são executadas main()— a função principal inicia apenas depois de todas as inicializações
package main
import "fmt"
var mensagem = inicializarMensagem()
func inicializarMensagem() string {
fmt.Println("1. Variável de pacote inicializada")
return "Olá, Go!"
}
func init() {
fmt.Println("2. init() executada")
fmt.Println(" mensagem =", mensagem)
}
func main() {
fmt.Println("3. main() executada")
}
Saída:
1. Variável de pacote inicializada
2. init() executada
mensagem = Olá, Go!
3. main() executada
Ordem entre packages
Quando um programa importa múltiplos packages, a ordem de inicialização é determinada pelo grafo de dependências:
main importa → pkg_a importa → pkg_c
main importa → pkg_b importa → pkg_c
Ordem: pkg_c.init() → pkg_a.init() → pkg_b.init() → main.init() → main.main()
O package pkg_c é inicializado primeiro porque é dependência de ambos. Cada package é inicializado apenas uma vez, independentemente de quantas vezes é importado.
Múltiplas funções init por arquivo
Diferente de qualquer outra função em Go, você pode declarar múltiplas funções init() no mesmo arquivo. Elas são executadas na ordem em que aparecem:
package config
import "fmt"
func init() {
fmt.Println("Primeira init: configurando logger")
setupLogger()
}
func init() {
fmt.Println("Segunda init: carregando configurações")
loadConfig()
}
func init() {
fmt.Println("Terceira init: validando ambiente")
validateEnvironment()
}
Embora isso seja permitido, ter múltiplas init() em um arquivo pode dificultar a leitura. Na prática, a maioria dos projetos usa uma única init() por arquivo.
Múltiplas funções init por package
Quando um package tem múltiplos arquivos .go, cada arquivo pode ter suas próprias funções init(). A ordem de execução entre arquivos segue a ordem lexicográfica dos nomes de arquivo:
meu_package/
├── a_setup.go → init() executa primeiro
├── b_database.go → init() executa segundo
└── c_handlers.go → init() executa terceiro
Essa ordenação é garantida pela especificação da linguagem, então é determinística e previsível.
Side-effect imports (blank import)
Uma das aplicações mais comuns de init() é o side-effect import — importar um package apenas para que sua init() execute, sem usar nenhum símbolo exportado diretamente:
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq" // registra driver PostgreSQL
_ "github.com/go-sql-driver/mysql" // registra driver MySQL
_ "image/png" // registra decoder PNG
)
func main() {
// O driver PostgreSQL foi registrado pela init() do lib/pq
db, err := sql.Open("postgres", "postgresql://localhost/meubanco")
if err != nil {
fmt.Println("Erro:", err)
return
}
defer db.Close()
}
O _ (blank identifier) indica que você está importando o package apenas por seus efeitos colaterais — a init() dentro de lib/pq chama sql.Register("postgres", &Driver{}) para registrar o driver no sistema global do package database/sql.
Como funciona o registro de drivers
Dentro do package lib/pq, existe algo como:
package pq
import "database/sql"
func init() {
sql.Register("postgres", &Driver{})
}
Esse padrão é usado extensivamente no ecossistema Go:
| Package | O que a init() faz |
|---|---|
database/sql drivers | Registra driver de banco de dados |
image/png, image/jpeg | Registra decoder de imagem |
net/http/pprof | Registra handlers de profiling |
crypto/... | Registra algoritmos criptográficos |
encoding/... | Registra codecs |
Init vs padrão de construtor
Em muitos casos, o padrão de construtor (uma função que retorna uma struct inicializada) é preferível a init():
// ABORDAGEM COM INIT — estado global, difícil de testar
package database
var DB *sql.DB
func init() {
var err error
DB, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err) // programa morre na inicialização
}
}
// ABORDAGEM COM CONSTRUTOR — explícita, testável
package database
type Config struct {
URL string
}
func NewConnection(cfg Config) (*sql.DB, error) {
db, err := sql.Open("postgres", cfg.URL)
if err != nil {
return nil, fmt.Errorf("erro ao conectar: %w", err)
}
return db, nil
}
A abordagem com construtor é preferível porque:
- Retorna errors em vez de fazer
log.Fatal - Aceita configuração como parâmetro, facilitando testes
- É explícita — quem lê o código sabe quando a conexão é criada
- Permite múltiplas instâncias — não depende de estado global
Quando usar init()
Casos de uso legítimos para init():
- Registro de drivers e plugins — o padrão
sql.Registeré o caso canônico - Inicialização de tabelas de lookup complexas que são constantes em runtime
- Validação de invariantes que o programa não pode funcionar sem
- Configuração de logging global mínima
func init() {
// Verificar que variáveis de ambiente críticas existem
required := []string{"API_KEY", "DATABASE_URL", "JWT_SECRET"}
for _, env := range required {
if os.Getenv(env) == "" {
log.Fatalf("variável de ambiente obrigatória não definida: %s", env)
}
}
}
Quando evitar init()
Evite init() quando:
- A inicialização pode falhar —
init()não retorna error, forçandolog.Fataloupanic - O código precisa ser testável — estado global criado em
init()dificulta testes isolados - A ordem de execução importa — depender da ordem entre arquivos é frágil
- Há dependência de configuração externa — variáveis de ambiente podem não estar disponíveis durante testes
- O setup é custoso — inicializar conexões de banco, clientes HTTP ou caches em
init()torna o startup mais lento e dificulta testes unitários
A regra geral da comunidade Go é: se você pode usar uma função construtora em vez de init(), prefira a função construtora. Reserve init() para registros globais necessários (drivers, codecs) e validações mínimas de ambiente.
Depuração de init()
Para entender a ordem de inicialização do seu programa, uma técnica útil é adicionar logging temporário:
func init() {
if os.Getenv("DEBUG_INIT") != "" {
log.Printf("[init] %s inicializado", "meu_package")
}
// ... inicialização normal
}
Também é possível usar o package runtime para identificar qual arquivo está executando:
func init() {
_, file, line, _ := runtime.Caller(0)
log.Printf("[init] executando %s:%d", file, line)
}
Isso é especialmente útil em projetos grandes com muitos packages e dependências.
Exemplo completo: aplicação com init()
package main
import (
"database/sql"
"log"
"net/http"
"os"
_ "github.com/lib/pq" // side-effect: registra driver postgres
)
var logger *log.Logger
func init() {
// Configuração mínima de logging — caso de uso legítimo
logger = log.New(os.Stdout, "[APP] ", log.LstdFlags|log.Lshortfile)
logger.Println("Logger inicializado")
}
func main() {
// Conexão explícita no main — NÃO no init
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
logger.Fatalf("erro ao conectar ao banco: %v", err)
}
defer db.Close()
http.HandleFunc("/", handler)
logger.Println("Servidor iniciando na porta 8080")
logger.Fatal(http.ListenAndServe(":8080", nil))
}
Esse exemplo segue as boas práticas: init() faz apenas configuração mínima de logging, enquanto a conexão de banco — que pode falhar e precisa de tratamento de error — é feita explicitamente em main(). O side-effect import do driver PostgreSQL é o uso legítimo de init() no package lib/pq.
Para aprender mais sobre organização de código Go, veja nosso tutorial de Clean Architecture e o guia sobre packages.
Perguntas frequentes sobre Init em Go
Posso chamar init() diretamente?
Não. A função init() é reservada pelo runtime do Go e não pode ser chamada manualmente. Tentar chamá-la resulta em erro de compilação: undefined: init. Ela é executada automaticamente uma única vez durante a inicialização do package.
Em que ordem os packages são inicializados?
A ordem segue o grafo de dependências: packages importados são inicializados antes dos que os importam. Dentro de um package, os arquivos são processados em ordem lexicográfica (alfabética). Dentro de um arquivo, múltiplas init() são executadas na ordem em que aparecem no código fonte. Todo esse processo é determinístico.
Init é executada em testes?
Sim. Quando você executa go test, todas as funções init() dos packages importados são executadas normalmente. Isso pode causar problemas se uma init() tenta conectar a um banco de dados ou serviço externo. Por isso, prefira construtores explícitos para dependências que podem não estar disponíveis em ambiente de teste.
Quantas funções init posso ter em um arquivo?
Não há limite técnico. Você pode ter quantas init() quiser em um único arquivo .go, e todas serão executadas na ordem em que aparecem. Porém, na prática, a maioria dos projetos Go usa no máximo uma init() por arquivo para manter a clareza. Se você precisa de múltiplas etapas de inicialização, considere chamar funções nomeadas de dentro de uma única init().