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:

  1. Importações recursivas — todos os packages importados são inicializados primeiro, recursivamente
  2. Variáveis de pacote — declarações var em nível de package são avaliadas em ordem de dependência
  3. Funções init() — todas as funções init() do package são executadas
  4. 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:

PackageO que a init() faz
database/sql driversRegistra driver de banco de dados
image/png, image/jpegRegistra decoder de imagem
net/http/pprofRegistra 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():

  1. Registro de drivers e plugins — o padrão sql.Register é o caso canônico
  2. Inicialização de tabelas de lookup complexas que são constantes em runtime
  3. Validação de invariantes que o programa não pode funcionar sem
  4. 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 falharinit() não retorna error, forçando log.Fatal ou panic
  • 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().