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á:

  1. Inicialização de variáveis de pacote em pkg1 (se houver) e execução de seus init.
  2. Inicialização de variáveis de pacote em pkg2 e execução de seus init.
  3. Inicialização de variáveis de pacote em main e execução de seus init.
  4. 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 init apenas 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 usar TestMain para controlar a inicialização.

Referências

Exercícios

  1. Escreva um programa com duas funções init que 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
    main
    
  2. Crie dois pacotes: a e b. O pacote a tem uma variável X = 1 e um init que imprime o valor de X. O pacote b tem uma variável Y = X + 1 e um init que imprime Y. Em main, importe ambos e veja a ordem de execução. O que acontece?

    ✓ Resposta:

    Isso causa um erro de compilação porque b depende de a.X, mas a não importa b e vice-versa? Na verdade, b precisa importar a para usar X. A ordem de inicialização será: primeiro inicializa a (variável X e init), depois b (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 = 2
    
  3. Declare três variáveis de pacote com dependências: a = b + 1, b = c + 1, c = 1. Imprima os valores em um init. 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.

  4. Explique por que é arriscado usar init para configurar conexões de banco de dados em uma biblioteca. Como você evitaria esse problema?

    ✓ Resposta:

    Usar init para 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, o init será 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 usar init apenas para registrar drivers ou configurações que não dependem de estado externo.

  5. Crie um programa que use init para 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)
    }