Retour au blog

Le mot-clé import en Go : syntaxe, alias, imports anonymes et dot imports

Comment fonctionne le mot-clé import de Go : imports groupés, chemins d'import, pourquoi un import inutilisé fait échouer le build, alias, imports anonymes et dot imports, et résolution des cycles d'import.

Le mot-clé import en Go : syntaxe, alias, imports anonymes et dot imports

Le mot-clé import rend les noms exportés d'un autre package accessibles dans un fichier source Go. Les imports se placent juste après la clause package et avant toute autre déclaration, et chacun désigne un package par son chemin d'import, par exemple "net/http" ou "github.com/jackc/pgx/v5". Vous accédez ensuite au contenu de ce package par son nom, comme dans http.ListenAndServe. Un import peut aussi porter un alias, l'identificateur blank _ ou un point ., et chacune de ces formes change la façon dont le nom du package est lié dans le fichier (spécification Go).

En bref

  • Les imports viennent après package et avant tout le reste. La plupart des fichiers utilisent un seul bloc groupé import ( ... ).
  • Un chemin d'import est une chaîne de caractères. Les chemins de la bibliothèque standard sont courts ("encoding/json"), et tous les autres commencent par un chemin de module issu de go.mod.
  • Un import inutilisé est une erreur de compilation : "os" imported and not used. goimports et gopls ajoutent et suppriment les imports à votre place.
  • alias "path" renomme un package à l'intérieur d'un seul fichier. Utilisez-le quand deux imports portent le même nom, comme html/template et text/template.
  • _ "path" importe un package uniquement pour ses effets de bord, qui s'exécutent dans ses fonctions init. Les drivers de base de données et net/http/pprof fonctionnent ainsi.
  • . "path" fait entrer les noms dans le fichier sans qualificatif. Les guides de style le déconseillent et staticcheck le signale comme ST1001, hormis de rares cas de test.
  • Go refuse les cycles d'import. La solution est un nouveau package partagé ou une interface définie là où elle est utilisée.
  • Les imports ont une portée limitée au fichier. Chaque fichier d'un package importe ce qu'il utilise, même quand un fichier voisin importe déjà le même package.

Comment importer un package en Go ?

Écrivez import suivi du chemin du package entre guillemets doubles. Un fichier qui n'a besoin que d'un package peut se contenter d'une seule ligne, et un fichier qui en utilise plusieurs emploie un bloc groupé entre parenthèses :

example.gogo
package main

import "fmt"

func main() {
	fmt.Println("ok")
}
example.gogo
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type healthResponse struct {
	Status string `json:"status"`
}

func main() {
	http.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(healthResponse{Status: "ok"})
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Les deux formes sont équivalentes, et presque tout le code Go utilise le bloc groupé. Les imports viennent toujours après la clause package et avant tout const, var, type ou func. Placez-en un plus bas et le parser s'arrête avec syntax error: imports must appear before other declarations. Il est impossible non plus d'importer à l'intérieur d'une fonction.

Les outils de formatage ordonnent le bloc pour vous. gofmt trie les lignes de chaque groupe par chemin d'import. goimports, que la plupart des éditeurs lancent à l'enregistrement, divise en plus le bloc en deux groupes séparés par une ligne vide, avec la bibliothèque standard en premier et tout le reste ensuite :

example.gogo
import (
	"context"
	"fmt"
	"net/http"

	"example.com/shop/billing"
	"github.com/jackc/pgx/v5"
)

L'ordre n'a aucun effet sur le programme. Il garde les diffs courts et montre d'un coup d'œil au lecteur quelles dépendances viennent de l'extérieur de la bibliothèque standard.

Qu'est-ce qu'un chemin d'import en Go ?

Un chemin d'import est la chaîne qui indique à la commande go où se trouve un package. Les packages de la bibliothèque standard ont des chemins courts sans point dans le premier élément, comme "fmt", "net/http" ou "crypto/rand". Le chemin de tout autre package commence par un chemin de module.

Vos propres packages utilisent le chemin de module déclaré dans go.mod, suivi du répertoire. Avec module example.com/shop dans go.mod, le package situé dans ./billing s'importe sous la forme "example.com/shop/billing". Les imports relatifs comme "./billing" n'existent pas en mode module, donc un package local s'importe toujours par son chemin complet.

Les packages tiers fonctionnent de la même manière, et la commande go les résout via go.mod :

  1. go get github.com/jackc/pgx/v5 télécharge le module et ajoute une ligne require à go.mod.
  2. Vous écrivez "github.com/jackc/pgx/v5" dans le bloc d'import.
  3. go mod tidy ajoute ensuite tout module dont vos imports ont besoin et retire ceux que plus rien n'importe.

Le dernier élément du chemin correspond généralement au nom du package, mais pas toujours. "github.com/jackc/pgx/v5" fournit un package nommé pgx, et "gopkg.in/yaml.v3" fournit yaml. Le mot-clé package en Go explique le lien entre noms et chemins.

Pourquoi Go refuse-t-il de compiler les imports inutilisés ?

Go traite un import inutilisé comme une erreur, et non comme un avertissement. Laissez "os" dans un fichier qui ne l'appelle plus et go build s'arrête :

example.texttext
./main.go:5:2: "os" imported and not used

La FAQ de Go en donne la raison. Un import inutilisé ralentit la compilation et ajoute une dépendance dont le programme n'a pas besoin, et dans une grande base de code ces dépendances s'accumulent. Les développeurs ignorent les avertissements, alors Go en fait une erreur. go mod tidy fait le même ménage pour go.mod, en retirant les dépendances que rien n'importe.

En pratique, vous corrigez rarement ces erreurs à la main. goimports et gopls, le serveur de langage Go derrière VS Code et le support Go de GoLand, suppriment les imports inutilisés et ajoutent ceux qui manquent à l'enregistrement. Si vous déboguez et voulez garder un import une minute de plus, affectez-en un élément à l'identificateur blank, comme dans var _ = os.Exit, et supprimez la ligne avant de faire votre commit.

Comment renommer un import en Go ?

Placez un alias devant le chemin. L'alias remplace le nom du package pour ce fichier uniquement :

example.gogo
import htmltemplate "html/template"

La raison habituelle est un conflit de noms. html/template et text/template s'appellent tous deux template, et un service d'e-mail a souvent besoin des deux : du texte brut pour l'objet et une sortie échappée en HTML pour le corps du message. Importer les deux sans alias échoue avec template redeclared in this block. Un alias sur l'un des deux règle le problème :

example.gogo
package main

import (
	htmltemplate "html/template"
	"os"
	"text/template"
)

func main() {
	subject := template.Must(template.New("subject").Parse("Your invoice {{.ID}}\n"))
	body := htmltemplate.Must(htmltemplate.New("body").Parse("<p>{{.Note}}</p>\n"))

	data := map[string]string{"ID": "INV-42", "Note": "<script>alert(1)</script>"}
	subject.Execute(os.Stdout, data)
	body.Execute(os.Stdout, data)
}

La sortie montre pourquoi les deux packages existent séparément. L'objet s'affiche sous la forme Your invoice INV-42, et le corps sous la forme <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>, échappé par html/template.

Les alias apparaissent aussi quand de nombreux packages partagent un dernier élément générique. Le code de Kubernetes importe plusieurs packages qui s'appellent tous v1, il écrit donc corev1 "k8s.io/api/core/v1" et metav1 "k8s.io/apimachinery/pkg/apis/meta/v1". En l'absence de conflit, gardez le vrai nom. Tout le monde reconnaît http., alors qu'un alias maison renvoie le lecteur au bloc d'import pour comprendre ce qu'il désigne.

Que fait un import anonyme _ en Go ?

Un import anonyme charge un package sans lier son nom. Vous ne pouvez rien y appeler, mais le package est tout de même initialisé : ses variables de niveau package reçoivent leur valeur et ses fonctions init s'exécutent. Certains packages font leur travail utile dans init en s'enregistrant auprès d'un autre package.

L'exemple le plus courant est un driver database/sql :

example.gogo
package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
	db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
}

Le package stdlib de pgx appelle sql.Register("pgx", ...) dans son init. Votre code ne s'adresse qu'à database/sql, mais sans l'import anonyme le driver ne s'enregistre jamais et sql.Open renvoie sql: unknown driver "pgx" (forgotten import?).

Autres imports anonymes que vous croiserez dans de vrais services :

ImportCe que fait son init
_ "github.com/jackc/pgx/v5/stdlib"Enregistre le driver pgx auprès de database/sql
_ "net/http/pprof"Ajoute les handlers de profilage /debug/pprof/ à http.DefaultServeMux
_ "image/png"Enregistre le décodeur PNG pour que image.Decode puisse lire les fichiers PNG
_ "time/tzdata"Embarque la base de données des fuseaux horaires pour les conteneurs qui n'en ont pas
_ "embed"Rien à l'exécution. Il est requis pour que //go:embed fonctionne sur une variable string ou []byte

Le cas de embed relève d'une règle de la toolchain plutôt que d'un effet de bord de init. Omettez l'import et le build échoue avec go:embed requires import "embed" (or import _ "embed", if package is not used).

L'ordre d'initialisation est prévisible. Chaque package importé est entièrement initialisé, y compris ses propres imports, avant le package qui l'importe. Depuis Go 1.21, la spécification fixe aussi l'ordre entre packages sans lien : ils sont initialisés dans l'ordre de leurs chemins d'import. Au sein d'un même package, les variables de niveau package sont initialisées d'abord, puis les fonctions init s'exécutent dans l'ordre où les fichiers sont présentés au compilateur, que la commande go trie par nom de fichier. main s'exécute en dernier. Un package importé par plusieurs autres n'est initialisé qu'une seule fois.

Placez les imports anonymes dans main ou dans le package qui a réellement besoin de l'effet de bord, pas dans une bibliothèque importée par d'autres. Une bibliothèque qui importe net/http/pprof de manière anonyme expose des endpoints de profilage dans chaque programme qui l'utilise, que ce programme le veuille ou non.

Faut-il utiliser les dot imports en Go ?

Presque jamais. Un dot import fusionne les noms exportés d'un package dans le fichier, si bien que vous les appelez sans le nom du package :

example.gogo
import . "strings"

func normalizeEmail(email string) string {
	return ToLower(TrimSpace(email))
}

Dans une fonction de trois lignes, cela reste facile à suivre. Dans un long fichier, un lecteur qui voit ToLower ne peut pas savoir si la fonction est définie dans ce package ni de quel import elle provient. La page Go Code Review Comments le déconseille, et staticcheck le signale :

example.texttext
main.go:3:8: should not use dot imports (ST1001)

Le seul usage accepté est un test qui doit vivre en dehors du package qu'il teste à cause d'un cycle. Code Review Comments donne l'exemple de package foo_test qui importe bar/testutil, lequel importe lui-même foo. Un dot import de foo permet à ce test de se lire comme s'il était dans le package. Certains frameworks de test, comme Ginkgo et Gomega, documentent aussi les dot imports pour leurs matchers. En dehors de ces cas, écrivez le nom du package.

Qu'est-ce qu'un cycle d'import en Go ?

Un cycle d'import désigne deux packages ou plus qui s'importent mutuellement, directement ou via une chaîne. Go les interdit, donc si billing importe customers pendant que customers importe billing, le build échoue :

example.texttext
package example.com/shop/billing
	imports example.com/shop/customers from billing.go
	imports example.com/shop/billing from customers.go: import cycle not allowed

Cette règle garantit un ordre d'initialisation bien défini et des builds rapides, car le compilateur peut toujours compiler un package après tout ce dont il dépend. Elle révèle aussi quelque chose sur la conception : un cycle signifie généralement que deux packages n'en forment en réalité qu'un seul, ou que tous deux dépendent d'un élément qui appartient à un troisième.

Il existe deux corrections courantes :

  1. Déplacer la partie partagée dans son propre package. Si billing et customers ont tous deux besoin d'un type CustomerID, placez-le dans un petit package shop/ids ou shop/domain qui n'importe ni l'un ni l'autre.
  2. Définir une interface là où elle est utilisée. Si billing n'a besoin que de retrouver l'e-mail d'un client, il peut déclarer ce dont il a besoin et laisser main lui fournir l'implémentation réelle :
example.gogo
package billing

import "context"

// CustomerLookup is the one thing billing needs from the customers package.
type CustomerLookup interface {
	Email(ctx context.Context, customerID string) (string, error)
}

type Service struct {
	customers CustomerLookup
}

func NewService(customers CustomerLookup) *Service {
	return &Service{customers: customers}
}

Désormais, billing n'importe plus customers, et customers peut importer billing librement. Tout type doté d'une méthode Email correspondante satisfait l'interface sans la nommer, si bien que la dépendance ne va que dans un sens.

La commande go applique une règle voisine à package main. Aucun package ne peut l'importer, et toute tentative donne import "example.com/shop/cmd/api" is a program, not an importable package.

Les imports Go ont-ils une portée fichier ou package ?

Les imports ont une portée fichier. Un import ne lie le nom du package que dans le fichier qui le contient, alors que tous les autres noms de niveau package sont partagés entre tous les fichiers du package. Ici, server.go importe log :

example.gogo
package main

import "log"

func main() {
	log.Println("server starting")
	runJobs()
}

Et jobs.go, dans le même package, appelle log sans l'importer :

example.gogo
package main

func runJobs() {
	log.Println("running jobs")
}

runJobs est visible dans server.go parce que les fonctions de niveau package sont partagées. L'import de log ne l'est pas, donc le build échoue :

example.texttext
./jobs.go:4:2: undefined: log

Chaque fichier liste ses propres imports, et goimports les ajoute pour vous. Cela signifie aussi qu'un nom d'import peut entrer en conflit avec une déclaration de niveau package située dans un autre fichier. Déclarez var log = ... dans un fichier pendant qu'un autre fichier importe log et vous obtenez log already declared through import of package log ("log").

Les alias ont eux aussi une portée fichier. Renommer mrand "math/rand/v2" dans un fichier n'a aucun effet sur les autres fichiers du package.

Go restreint également les packages que vous avez le droit d'importer. Un package situé sous un répertoire internal/ ne peut être importé que par du code enraciné dans le parent de internal. Le mot-clé package en Go traite de internal/ et du fonctionnement de la visibilité entre packages.

La place de LevelUpGo

LevelUpGo enseigne Go au moyen d'exercices qui exécutent du vrai code Go dans le navigateur. Le cours Packages & Organization couvre les noms exportés, init et l'état des packages, les modules Go et les dépendances, ainsi que les commandes de module que vous lancez tous les jours, comme go get et go mod tidy. Si vous débutez, commencez par Go Basics. Pour les 24 autres mots réservés, consultez Les mots-clés Go : les 25 expliqués.

Questions fréquentes

import est-il un mot-clé en Go ?

Oui. import fait partie des 25 mots-clés réservés de Go, vous ne pouvez donc pas l'utiliser comme nom de variable, de fonction ou de package. Il ne peut apparaître que dans les déclarations d'import en haut d'un fichier.

Peut-on importer un package dans une fonction en Go ?

Non. Les imports ne sont autorisés qu'au niveau supérieur d'un fichier, juste après la clause package. Un import dans le corps d'une fonction échoue avec syntax error: unexpected keyword import. Si vous n'avez besoin d'un package que dans une seule fonction, importez-le quand même en haut du fichier.

Comment importer un package local en Go ?

Utilisez le chemin de module de go.mod suivi du répertoire du package. Dans un module nommé example.com/shop, le package situé dans ./internal/pricing s'importe sous la forme "example.com/shop/internal/pricing". Les modules Go ne prennent pas en charge les imports relatifs comme "./pricing".

Quelle est la différence entre import _ et import . en Go ?

import _ "path" exécute l'initialisation du package mais ne vous donne aucun moyen d'y faire référence. Il sert aux effets de bord, comme l'enregistrement d'un driver de base de données. import . "path" fait l'inverse et place tous les noms exportés du package directement dans votre fichier, pour que vous puissiez les appeler sans qualificatif. Les imports anonymes sont courants en Go idiomatique, alors que les dot imports sont déconseillés.

L'ordre des imports a-t-il une importance en Go ?

Non. Le compilateur ne se soucie pas de l'ordre des lignes dans un bloc d'import, et l'ordre d'initialisation est déterminé par le graphe de dépendances, pas par la façon dont vous listez les imports. gofmt les trie par chemin et goimports sépare la bibliothèque standard des autres packages, mais c'est uniquement pour la lisibilité.

Sources

Écrivez du Go comme un ingénieur senior

Des leçons interactives dans votre navigateur. Les premières sont gratuites.

Essayer une leçon gratuiteOu créer un compte gratuit