O que está por trás do sublinhado no import? Guia para quem quer entender Go mais a fundo
Você já abriu um código Go de outra pessoa e viu uma linha como import _ "github.com/lib/pq"? A primeira reação — "Isso é legal? O compilador não vai reclamar?". Spoiler: não vai reclamar. Mais do que isso, é um dos recursos mais elegantes da linguagem, que frequentemente gera dúvidas em iniciantes. Vamos entender o que esse misterioso símbolo de sublinhado antes do import realmente faz em Go e como usá-lo com sabedoria.
No Stack Overflow, essa pergunta acumulou milhares de visualizações — e não é surpresa. A sintaxe parece um erro de digitação, mas por trás dela existe um conceito profundo de inicialização de pacotes e efeitos colaterais. Neste artigo, não vamos apenas dar uma definição seca, mas mostrar cenários reais onde o _ é indispensável. Vamos lá!
Problema: "Importei, mas não uso"
Em Go, existe uma regra rigorosa: cada pacote importado deve ser usado. Caso contrário, o código não compila. Isso foi feito para garantir limpeza e desempenho — sem peso morto. Mas o que fazer quando precisamos apenas executar o código de um pacote na inicialização do programa, sem chamar suas funções diretamente? É aí que entra o blank identifier (identificador vazio) — o símbolo _.
Quando você escreve:
import _ "some/package"— você está dizendo ao compilador: "Eu sei que não vou chamar funções deste pacote, mas, por favor, execute sua função init()". O Go realiza a inicialização, mas sem gerar o erro "unused import".
Como funciona o init() e por que é importante
Cada pacote em Go pode conter uma ou mais funções init(). Elas são executadas automaticamente quando o pacote é importado, antes da função main(). A ordem de execução é estritamente definida:
- Primeiro, as variáveis do pacote são inicializadas
- Em seguida,
init()é chamada - Se o pacote importa outros pacotes — eles são inicializados antes
Exemplo para ilustrar. Vamos criar um pacote config:
// config/config.gopackage config
var AppName string
func init() { AppName = "MyApp v1.0" println("Config initialized:", AppName)}Agora, no main.go, podemos importar este pacote "silenciosamente":
// main.gopackage main
import ( _ "example.com/project/config" "fmt")
func main() { fmt.Println("Main function started")}Resultado da execução:
Config initialized: MyApp v1.0Main function startedObserve: não chamamos config.AppName — apenas importamos o pacote com sublinhado, e seu init() foi executado automaticamente. Essa é a mecânica básica sobre a qual muitas soluções reais são construídas.
Cenário principal: registro de drivers de banco de dados
O exemplo mais comum de uso de import _ em código de produção é o registro de drivers de banco de dados. O pacote padrão database/sql usa o padrão "fábrica": todos os drivers são registrados através de sql.Register() em suas funções init().
Veja como o PostgreSQL é conectado (driver lib/pq):
import ( "database/sql" _ "github.com/lib/pq" // registra o driver "postgres")
func main() { db, err := sql.Open("postgres", "user=... dbname=...") // ...}Internamente, no lib/pq, dentro do init() há algo como:
func init() { sql.Register("postgres", &Driver{})}Sem a importação vazia, o driver não seria registrado, e sql.Open("postgres", ...) retornaria um erro "unknown driver". O mesmo funciona para MySQL, SQLite, Oracle e outros bancos de dados.