Que cache l'underscore dans import ? Guide pour ceux qui veulent comprendre Go plus en profondeur
\n\nAvez-vous déjà ouvert du code Go écrit par quelqu'un d'autre et vu une ligne comme import _ "github.com/lib/pq" ? La première réaction — « Est-ce que c'est légal ? Le compilateur ne va-t-il pas râler ? ». Spoiler : il ne râlera pas. Bien plus, c'est l'une des fonctionnalités les plus élégantes du langage, qui soulève souvent des questions chez les débutants. Analysons ensemble ce que fait réellement ce mystérieux symbole underscore avant l'import en Go, et comment l'utiliser intelligemment.
Sur Stack Overflow, cette question a recueilli des milliers de vues — et ce n'est pas surprenant. La syntaxe ressemble à une faute de frappe, mais derrière elle se cache un concept profond d'initialisation de paquets et d'effets de bord. Dans cet article, nous ne donnerons pas simplement une définition sèche, mais nous montrerons des scénarios réels où l'on ne peut pas se passer de _. C'est parti !
Problème : « J'ai importé, mais je n'utilise pas »
\n\nEn Go, il existe une règle stricte : chaque paquet importé doit être utilisé. Sinon, le code ne se compile pas. Cela a été fait pour la propreté et la performance — pas de poids mort. Mais que faire si nous avons simplement besoin d'exécuter le code d'un paquet au démarrage du programme, sans appeler directement ses fonctions ? C'est là qu'intervient le blank identifier (identifiant vide) — le symbole _.
Lorsque vous écrivez :
\nimport _ "some/package"\n— vous dites au compilateur : « Je sais que je n'appellerai pas les fonctions de ce paquet, mais s'il te plaît, exécute sa fonction init() ». Go effectuera l'initialisation, mais sans générer l'erreur « unused import ».
Comment fonctionne init() et pourquoi c'est important
\n\nChaque paquet en Go peut contenir une ou plusieurs fonctions init(). Elles s'exécutent automatiquement lors de l'import du paquet, avant la fonction main(). L'ordre d'exécution est strictement défini :
- \n
- D'abord, les variables du paquet sont initialisées \n
- Ensuite,
init()est appelée \n - Si le paquet importe d'autres paquets — ils sont initialisés avant \n
Exemple pour illustrer. Créons un paquet config :
// config/config.go\npackage config\n\nvar AppName string\n\nfunc init() {\n AppName = "MyApp v1.0"\n println("Config initialized:", AppName)\n}\n\nMaintenant, dans main.go, nous pouvons importer ce paquet « silencieusement » :
\n// main.go\npackage main\n\nimport (\n _ "example.com/project/config"\n "fmt"\n)\n\nfunc main() {\n fmt.Println("Main function started")\n}\n\nRésultat de l'exécution :
\nConfig initialized: MyApp v1.0\nMain function started\n\nRemarquez : nous n'avons pas appelé config.AppName — nous avons simplement importé le paquet avec un underscore, et son init() s'est déclenché automatiquement. C'est le mécanisme de base sur lequel reposent de nombreuses solutions réelles.
Scénario principal : enregistrement des pilotes de bases de données
\n\nL'exemple le plus courant d'utilisation de import _ dans le code de production est l'enregistrement des pilotes de bases de données. Le paquet standard database/sql utilise le modèle « fabrique » : tous les pilotes sont enregistrés via sql.Register() dans leurs fonctions init().
Regardez comment se connecte PostgreSQL (pilote lib/pq) :
import (\n "database/sql"\n _ "github.com/lib/pq" // enregistre le pilote "postgres"\n)\n\nfunc main() {\n db, err := sql.Open("postgres", "user=... dbname=...")\n // ...\n}\n\nÀ l'intérieur de lib/pq, dans init(), on trouve quelque chose comme :
func init() {\n sql.Register("postgres", &Driver{})\n}\n\nSans l'import vide, le pilote ne serait pas enregistré, et sql.Open("postgres", ...) renverrait une erreur « unknown driver ». Cela fonctionne de la même manière pour MySQL, SQLite, Oracle et d'autres bases de données.