¿Cuándo se ejecuta init() en Go? Lo analizamos con un ejemplo real
Imagina: estás escribiendo un microservicio en Go. En uno de los paquetes declaraste init() para cargar la configuración. Ejecutas la aplicación — y de repente falla con nil pointer dereference. Entras en pánico: "¿Por qué? ¡Se suponía que la configuración se cargara primero!"
¿Te suena familiar? Si es así, no estás solo. La pregunta "¿Cuándo se ejecuta la función init()?" es una de las más populares en Stack Overflow (miles de visitas). Aclaremos de una vez por todas cuándo y en qué orden se ejecuta init() en Go, para que no caigas más en esta trampa.
¿Qué es init() y para qué sirve?
En Go, init() es una función especial sin parámetros ni valor de retorno. No se llama explícitamente. El runtime de Go la ejecuta automáticamente antes de ejecutar la función main().
¿Para qué sirve?
- Inicializar variables globales que requieren lógica compleja (por ejemplo, lectura de un archivo).
- Registrar controladores de bases de datos o manejadores HTTP.
- Verificar el entorno (variables de entorno, disponibilidad de recursos).
Pero la regla principal es: nunca debes confiar en el orden de ejecución de init() entre diferentes archivos de un mismo paquete. Y he aquí por qué.
¿Cuándo se ejecuta exactamente init()? El orden preciso
Go ejecuta init() siguiendo reglas estrictas. Vamos a desglosarlas paso a paso.
Regla n.º 1: Un paquete, un orden
Dentro de un mismo paquete, init() se ejecuta en el orden de declaración de los archivos (por orden alfabético de los nombres de archivo). Pero esto es un detalle de implementación — es mejor no basarse en ello.
Regla n.º 2: Primero la inicialización de variables, luego init()
Antes de llamar a init(), Go inicializa todas las variables globales del paquete. Si una variable depende de otra variable del mismo paquete, el orden de declaración es importante.
package config
var AppName string = "MyApp"var Version string = getVersion() // se llamará antes de init()
func init() { // Aquí ya están disponibles AppName y Version println("Init iniciado para", AppName)}
func getVersion() string { return "1.0.0"}Regla n.º 3: Los paquetes importados se inicializan primero
Esta es la regla más importante. Si el paquete A importa el paquete B, primero se ejecutarán todos los init() del paquete B, y solo después los de A. Go construye un grafo de dependencias y ejecuta la inicialización "de abajo hacia arriba".
// Paquete databasepackage database
func init() { println("1. database init")}
// Paquete main (importa database)package main
import "myapp/database"
func init() { println("2. main init")}
func main() { println("3. main function")}
// Salida:// 1. database init// 2. main init// 3. main functionRegla n.º 4: Varios init() en un mismo archivo — se puede, pero no es necesario
En un mismo archivo puede haber varios init(). Se ejecutarán en el orden de declaración. Pero esto dificulta la lectura del código. Es mejor unificar la lógica en una sola función.
Ejemplo práctico: ¿qué sucede si te equivocas con el orden?
Simulemos un error típico de principiante. Supongamos que tenemos un paquete config que lee la configuración de un archivo, y un paquete app que utiliza esa configuración.
// config.gopackage config
var Settings map[string]string
func init() { // Simulamos la lectura del archivo Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Configuración cargada")}
// app.gopackage app
import "myapp/config"
var ServerPort string
func init() { // ERROR: si config.Settings aún no se ha inicializado