Quand init() s'exécute-t-il en Go ? Analyse avec un exemple concret
Imaginez : vous écrivez un microservice en Go. Dans l'un des packages, vous avez déclaré init() pour charger la configuration. Vous lancez l'application — et soudain, elle plante avec une nil pointer dereference. Vous paniquez : "Pourquoi ? La configuration aurait dû être chargée en premier !"
Cela vous semble familier ? Si oui, vous n'êtes pas seul. La question "When does the init() function run?" est l'une des plus populaires sur Stack Overflow (des milliers de vues). Comprenons une fois pour toutes quand et dans quel ordre init() s'exécute en Go, afin que vous ne tombiez plus dans ce piège.
Qu'est-ce que init() et à quoi sert-il ?
En Go, init() est une fonction spéciale sans paramètres ni valeur de retour. Elle n'est pas appelée explicitement. C'est le runtime Go qui l'exécute automatiquement avant l'exécution de la fonction main().
À quoi cela sert-il ?
- Initialiser des variables globales qui nécessitent une logique complexe (par exemple, la lecture d'un fichier).
- Enregistrer des pilotes de base de données ou des gestionnaires HTTP.
- Vérifier l'environnement (variables d'environnement, disponibilité des ressources).
Mais la règle principale est la suivante : vous ne devez jamais vous fier à l'ordre d'exécution de init() entre différents fichiers d'un même package. Et voici pourquoi.
Quand exactement init() s'exécute-t-il ? L'ordre précis
Go exécute init() selon des règles strictes. Détaillons-les point par point.
Règle n°1 : Un package — un ordre
Au sein d'un même package, init() s'exécute dans l'ordre de déclaration des fichiers (par ordre alphabétique des noms de fichiers). Mais c'est un détail d'implémentation — il vaut mieux ne pas s'y fier.
Règle n°2 : D'abord l'initialisation des variables, ensuite init()
Avant d'appeler init(), Go initialise toutes les variables globales du package. Si une variable dépend d'une autre variable du même package, l'ordre de leur déclaration est important.
package config
var AppName string = "MyApp"var Version string = getVersion() // sera appelée avant init()
func init() { // AppName et Version sont déjà disponibles ici println("Init started for", AppName)}
func getVersion() string { return "1.0.0"}Règle n°3 : Les packages importés sont initialisés en premier
C'est la règle la plus importante. Si le package A importe le package B, alors tous les init() du package B s'exécuteront en premier, et ensuite seulement ceux du package A. Go construit un graphe de dépendances et effectue l'initialisation "de bas en haut".
// Package databasepackage database
func init() { println("1. database init")}
// Package main (importe database)package main
import "myapp/database"
func init() { println("2. main init")}
func main() { println("3. main function")}
// Sortie :// 1. database init// 2. main init// 3. main functionRègle n°4 : Plusieurs init() dans un même fichier — possible, mais déconseillé
Un même fichier peut contenir plusieurs init(). Ils s'exécuteront dans l'ordre de leur déclaration. Mais cela rend la lecture du code plus complexe. Il est préférable de regrouper la logique dans une seule fonction.
Exemple concret : que se passe-t-il si l'on se trompe dans l'ordre ?
Simulons une erreur typique de débutant. Supposons que nous ayons un package config qui lit les paramètres depuis un fichier, et un package app qui utilise ces paramètres.
// config.gopackage config
var Settings map[string]string
func init() { // Simulation de la lecture d'un fichier Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Config loaded")}
// app.gopackage app
import "myapp/config"
var ServerPort string
func init() { // ERREUR : si config.Settings n'est pas encore initialisé