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
packageet 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 dego.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, commehtml/templateettext/template._ "path"importe un package uniquement pour ses effets de bord, qui s'exécutent dans ses fonctionsinit. Les drivers de base de données etnet/http/pproffonctionnent 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.gogopackage main import "fmt" func main() { fmt.Println("ok") }
example.gogopackage 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.gogoimport ( "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 :
go get github.com/jackc/pgx/v5télécharge le module et ajoute une lignerequireàgo.mod.- Vous écrivez
"github.com/jackc/pgx/v5"dans le bloc d'import. go mod tidyajoute 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.gogoimport 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.gogopackage 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><script>alert(1)</script></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.gogopackage 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 :
| Import | Ce 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.gogoimport . "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.texttextmain.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.texttextpackage 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 :
- Déplacer la partie partagée dans son propre package. Si
billingetcustomersont tous deux besoin d'un typeCustomerID, placez-le dans un petit packageshop/idsoushop/domainqui n'importe ni l'un ni l'autre. - Définir une interface là où elle est utilisée. Si
billingn'a besoin que de retrouver l'e-mail d'un client, il peut déclarer ce dont il a besoin et laissermainlui fournir l'implémentation réelle :
example.gogopackage 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.gogopackage main import "log" func main() { log.Println("server starting") runJobs() }
Et jobs.go, dans le même package, appelle log sans l'importer :
example.gogopackage 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
- The Go Programming Language Specification, Import declarations : https://go.dev/ref/spec#Import_declarations
- The Go Programming Language Specification, Package initialization : https://go.dev/ref/spec#Package_initialization
- Effective Go, The blank identifier in imports : https://go.dev/doc/effective_go#blank_import
- Go FAQ, Unused variables and imports : https://go.dev/doc/faq#unused_variables_and_imports
- Command go, Import path syntax : https://pkg.go.dev/cmd/go#hdr-Import_path_syntax
- Command go, Internal directories : https://pkg.go.dev/cmd/go#hdr-Internal_Directories
- Package database/sql : https://pkg.go.dev/database/sql
- Package embed : https://pkg.go.dev/embed
- Go Code Review Comments, Import blank and import dot : https://go.dev/wiki/CodeReviewComments#import-blank
- goimports : https://pkg.go.dev/golang.org/x/tools/cmd/goimports
- Staticcheck ST1001 : https://staticcheck.dev/docs/checks/#ST1001
