Go tiene reglas estrictas para la inicialización de paquetes, variables y funciones al iniciar un programa. El mecanismo principal es la ejecución de funciones init y la inicialización de variables globales. Comprender correctamente estos procesos es importante para prevenir errores y efectos inesperados.
Historia de la pregunta:
En Go, desde el principio, se introdujo una clara división de fases de inicio: declaración, inicialización y ejecución posterior del código. En lenguajes como C/C++, a menudo se utilizan constructores para variables globales; en Go, el orden de inicialización es determinista, pero tiene sus matices.
Problema:
Es fácil caer en la trampa cuando la inicialización de variables globales o la llamada a init lleva a situaciones de dependencia mutua o cíclica entre paquetes. Esto puede ser difícil de rastrear, y los programas pueden comportarse de manera inesperada, especialmente con dependencias ocultas o la encapsulación del estado al inicio.
Solución:
Los paquetes en Go se inicializan en un orden determinado por sus dependencias: primero las dependencias, luego el propio paquete. Primero se inicializan las variables de nivel de paquete (en el orden en que aparecen en el archivo fuente), luego se llama a cualquier función init(), si existe. Se pueden declarar múltiples init() en un mismo archivo. El orden de inicialización entre archivos de un mismo paquete no está definido (y esto puede llevar a errores).
Ejemplo de código:
// a.go package main import "fmt" func init() { fmt.Println("init from a.go") } // b.go package main import "fmt" func init() { fmt.Println("init from b.go") }
El resultado de la ejecución de estas funciones init no es predecible entre archivos de un mismo directorio, pero siempre ocurre antes de la función main().
Características clave:
¿Se puede confiar en el orden de ejecución de las funciones init en diferentes archivos de un mismo paquete?
¡No! Go no garantiza el orden entre las funciones init de diferentes archivos en un mismo paquete. Las esperanzas de un orden determinado pueden dar lugar a errores difíciles de detectar y a la descomposición de la lógica empresarial.
¿Pueden las variables globales no estar inicializadas en el momento de ejecutar la función init?
No: todas las variables globales del paquete se ejecutan estrictamente en el orden de declaración antes de todas las funciones init de ese paquete. Las excepciones son solo las inicializaciones cruzadas entre paquetes (ver más abajo).
¿Cómo evitar las dependencias cíclicas init entre paquetes?
Go no permite importaciones cíclicas a nivel de paquetes (esto es un error de tiempo de compilación), pero se puede caer en la trampa de la inicialización indirecta: A depende de B, B de C, y C (a través de una variable global o init) llama código de A. En tales casos, puede surgir un orden de llamada a init/constructores globales que no es obvio.
En el equipo, la lógica de inicialización de servicios se ejecuta en varias funciones init de diferentes archivos. Una init depende del resultado de otra, lo que provoca un comportamiento aleatorio entre compilaciones y en diferentes servidores.
Ventajas:
Desventajas:
Todo el estado y la inicialización se realizan mediante llamadas explícitas en main(). Las funciones init se utilizan exclusivamente para rastrear el inicio y realizar pequeñas verificaciones.
Ventajas:
Desventajas: