Go a des règles strictes concernant l'initialisation des paquets, des variables et des fonctions lors du démarrage d'un programme. Le mécanisme principal est l'exécution des fonctions init et l'initialisation des variables globales. Une bonne compréhension de ces processus est cruciale pour éviter les erreurs et les effets inattendus.
Historique de la question :
Go a introduit dès le départ une séparation stricte des phases de démarrage : déclaration, initialisation et exécution ultérieure du code. Dans les langages comme C/C++, des constructeurs de variables globales sont souvent utilisés, tandis qu'en Go, l'ordre d'initialisation est déterminé, mais il y a des nuances.
Problème :
Il est facile de tomber dans le piège où l'initialisation des variables globales ou l'appel de init entraîne des situations de dépendance mutuelle ou cyclique entre les paquets. Cela peut être difficile à détecter, et les programmes peuvent se comporter de manière inattendue, surtout avec des dépendances cachées ou une encapsulation de l'état au démarrage.
Solution :
Les paquets en Go sont initialisés dans un ordre déterminé par leurs dépendances : d'abord les dépendances, puis le paquet lui-même. Les variables de niveau paquet sont initialisées en premier (dans l'ordre d'apparition dans le fichier source), puis toute fonction init() est appelée, si elle existe. Plusieurs init() peuvent être déclarées dans un même fichier. L'ordre d'initialisation entre les fichiers d'un même paquet n'est pas défini (ce qui peut entraîner des erreurs).
Exemple de code :
// 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") }
Le résultat de l'exécution de ces fonctions init n'est pas prévisible entre les fichiers d'un même répertoire, mais cela se produit toujours avant la fonction main().
Caractéristiques clés :
Peut-on compter sur l'ordre d'exécution des fonctions init dans différents fichiers d'un même paquet ?
Non ! Go ne garantit pas l'ordre entre les fonctions init dans différents fichiers d'un même paquet. Espérer un ordre déterminé peut entraîner des erreurs difficiles à détecter et des problèmes dans la logique métier.
Les variables globales peuvent-elles ne pas être initialisées au moment de l'exécution de la fonction init ?
Non — toutes les variables globales du paquet sont exécutées strictement dans l'ordre de leur déclaration avant toutes les fonctions init de ce paquet. Les exceptions ne concernent que les initialisations croisées entre paquets (voir ci-dessous).
Comment éviter les dépendances cycliques init entre les paquets ?
Go n'autorise pas les imports cycliques au niveau des paquets (c'est une erreur de compilation), mais il est possible de tomber dans le piège d'une initialisation indirecte : A dépend de B, B de C, et C (via une variable globale ou init) appelle du code de A. Dans de tels cas, un ordre d'appel des init/globals constructeurs peut ne pas être évident.
Dans l'équipe, la logique d'initialisation des services a été réalisée dans plusieurs fonctions init dans différents fichiers. Une init dépend du résultat d'une autre, ce qui entraîne un comportement aléatoire entre les compilations et les exécutions sur différents serveurs.
Avantages :
Inconvénients :
Tout l'état et l'initialisation sont réalisés par des appels explicites dans main(). Les fonctions init sont utilisées exclusivement pour tracer le démarrage et effectuer de petites vérifications.
Avantages :
Inconvénients :