Le mot-clé package indique le package auquel appartient un fichier source Go, et il doit figurer sur la première ligne de code de chaque fichier .go. package main produit un programme exécutable, et tout autre nom, par exemple package billing, produit une bibliothèque que d'autres packages importent. Tous les fichiers d'un même répertoire doivent utiliser le même nom de package, et c'est ce nom que le code appelant écrit devant un point pour accéder à vos noms exportés, comme dans billing.Total (spécification Go).
En bref
- Chaque fichier
.gocommence parpackage name. Seuls des commentaires et des lignes vides peuvent le précéder. package mainest le programme. Il exige unefunc main()sans arguments ni valeurs de retour, etgo builden fait un binaire. Il ne peut pas être importé.- Un répertoire correspond à un package. Des fichiers portant des noms de package différents dans le même dossier échouent avec
found packages billing (invoice.go) and payments (tax.go). La principale exception concerne les packages_testdans les fichiers de test. - Le nom du package et le chemin d'import sont deux choses distinctes. Vous importez
"gopkg.in/yaml.v3"et appelezyaml.Unmarshal. Par convention, le nom correspond au dernier élément du chemin. - Un bon nom de package est un mot unique, court et en minuscules, sans underscores ni mixedCaps. Évitez
utiletcommon. - La visibilité se décide au niveau du package. Un nom commençant par une majuscule est exporté vers les autres packages. Un nom en minuscules est visible par tous les fichiers du même package, et nulle part ailleurs.
Que fait package main en Go ?
package main indique à la chaîne d'outils Go de produire un exécutable plutôt qu'une bibliothèque. La fonction main de ce package est le point de départ du programme, et le programme se termine lorsqu'elle retourne. Voici un service HTTP complet en un seul fichier :
example.gogopackage main import ( "fmt" "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "ok") }) log.Fatal(http.ListenAndServe(":8080", mux)) }
go run . le compile et l'exécute, et go build -o bin/api . écrit un binaire. La chaîne d'outils impose les deux règles liées à main. Un package main sans fonction main échoue à l'édition de liens :
example.texttextruntime.main_main·f: function main is undeclared in the main package
Et lancer un package de bibliothèque avec go run ./billing échoue avant même toute compilation :
example.texttextpackage example.com/shop/billing is not a main package
Comme main est le programme, aucun autre package ne peut l'importer. Toute tentative donne import "example.com/shop/cmd/api" is a program, not an importable package. Les projets Go gardent donc leur logique dans des packages de bibliothèque et laissent main réduit au minimum. Un dépôt qui produit plusieurs binaires place chacun d'eux dans son propre répertoire, généralement sous cmd/, et chacun de ces répertoires est un package main distinct. Le backend de LevelUpGo en compte 11, du serveur d'API à l'importeur de contenu, et tous partagent 22 packages de bibliothèque sous internal/.
Deux fichiers d'un même répertoire peuvent-ils avoir des noms de package différents ?
Non. La commande go traite un répertoire comme un seul package, donc chaque fichier .go qu'il contient doit déclarer le même nom. Imaginons que billing/invoice.go commence par package billing et que quelqu'un ajoute billing/tax.go avec package payments. go build ./billing s'arrête avec :
example.texttextfound packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing
La solution consiste à renommer l'une des clauses ou à déplacer le fichier dans son propre répertoire. Le répertoire est aussi la plus petite unité importable : si deux fichiers appartiennent à des packages différents, ils doivent se trouver dans des dossiers différents.
Répartir un package sur plusieurs fichiers ne coûte rien. Chaque fichier du répertoire voit les noms de tous les autres fichiers, exportés ou non, sans rien importer. Un package billing composé de invoice.go, tax.go et refund.go se comporte exactement comme un seul long fichier.
Quelle différence entre nom de package et chemin d'import ?
Le chemin d'import indique où se trouve le package, et le nom du package est l'identifiant que vous utilisez dans le code. Le chemin d'import est le chemin du module défini dans go.mod suivi du répertoire, par exemple example.com/shop/billing. Le nom du package provient de la clause package des fichiers de ce répertoire.
Par convention, les deux concordent : le nom est le dernier élément du chemin. Le tableau montre un chemin où ils correspondent et les deux exceptions habituelles, toutes deux dues aux versions :
| Chemin d'import | Nom du package | Pourquoi ils diffèrent |
|---|---|---|
net/http | http | Ils correspondent |
gopkg.in/yaml.v3 | yaml | Le chemin porte un suffixe de version .v3 |
math/rand/v2 | rand | Répertoire de version majeure /v2 |
github.com/jackc/pgx/v5 | pgx | Répertoire de version majeure /v5 |
Lorsque deux imports portent le même nom, vous en renommez un dans le bloc d'import. Du code qui génère des jetons de session et ajoute aussi du jitter aux nouvelles tentatives a souvent besoin à la fois de crypto/rand et de math/rand/v2, qui s'appellent tous deux rand :
example.gogopackage tokens import ( "crypto/rand" "encoding/hex" mrand "math/rand/v2" "time" ) // NewSessionID returns a random 32-character hex session ID. func NewSessionID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) } // RetryDelay adds up to 100ms of jitter to a base delay. func RetryDelay(base time.Duration) time.Duration { return base + time.Duration(mrand.IntN(100))*time.Millisecond }
Le renommage ne s'applique qu'à ce fichier. Tous les autres fichiers voient toujours le nom déclaré du package. Le mot-clé import en Go couvre les alias, les imports blancs et les autres formes d'import.
Un répertoire peut contenir un package dont le nom diffère de celui du dossier, et Go le compilera. Ce sont les lecteurs qui en paient le prix. Quiconque importe example.com/shop/httpapi-v2 doit ouvrir le code source pour apprendre que le package s'appelle httpapi. Gardez-les identiques, sauf si un suffixe de version impose une différence.
Comment nommer un package Go ?
Les noms de package Go suivent un petit ensemble de règles tirées d'Effective Go et de l'article Package names du blog Go :
- En minuscules, en un seul mot :
billing,httpapi,ratelimit. Pasrate_limitnirateLimit. La spécification les autorise, car un nom de package peut être n'importe quel identifiant, mais le code Go idiomatique ne les utilise pas. - Court et précis. Le nom est saisi à chaque point d'appel.
strconvvaut mieux questringconversion, etauthvaut mieux queauthenticationservice. - Nommé d'après ce qu'il fournit.
util,common,helpers,miscetmodelsne disent rien de leur contenu, et ils finissent par devenir des packages que tout le monde importe. Découpez-les plutôt par finalité, par exemplemoney,slugifyoupagination. - Ne répétez pas le nom du package dans les noms exportés, puisque les appelants l'écrivent déjà.
http.Serverest correct,http.HTTPServerne l'est pas.billing.Totalest correct,billing.BillingTotalne l'est pas. Le constructeur du type principal d'un package s'appelle souvent simplementNew, comme dansring.New. - Ni un mot-clé, ni
_.package typeest une erreur de syntaxe etpackage _échoue avecinvalid package name _.
Lisez le nom avec ses identifiants exportés, puisque c'est ainsi que chaque appelant le voit. ratelimit.New(100, time.Minute) et config.Load("app.yaml") se lisent comme des phrases. Ce n'est pas le cas de utils.NewRateLimiterUtil.
Comment package contrôle-t-il la visibilité en Go ?
Go n'a pas de mots-clés public, private ou protected. C'est le package qui sert de frontière. Un nom qui commence par une majuscule est exporté et visible par tout package qui l'importe. Un nom en minuscules n'est pas exporté et n'est visible qu'à l'intérieur de son propre package, dans tous ses fichiers.
example.gogo// Package billing calculates invoice totals and applies VAT. package billing // VATPercent is the standard VAT rate applied to invoices. const VATPercent = 25 // Total returns the sum of line items in cents, including VAT. func Total(lineItems []int64) int64 { var sum int64 for _, c := range lineItems { sum += c } return addVAT(sum) } func addVAT(cents int64) int64 { return cents + cents*VATPercent/100 }
Un handler dans package main peut appeler billing.Total, mais billing.addVAT(1000) échoue avec name addVAT not exported by package billing. La même règle s'applique aux champs et aux méthodes des structs. C'est aussi pour cette raison que encoding/json ne voit que les champs exportés d'un struct.
Pour du code partagé par plusieurs de vos packages mais que des modules extérieurs ne doivent pas importer, Go propose les répertoires internal. Un package situé sous example.com/shop/internal/pricing peut être importé par tout ce qui a pour racine example.com/shop, et par rien d'autre. Un autre module qui essaie obtient :
example.texttextuse of internal package example.com/shop/internal/pricing not allowed
Structure d'un projet Go explique quand recourir à internal/ et comment organiser cmd/ et les packages de bibliothèque dans un vrai service.
Qu'est-ce qu'un package _test en Go ?
Les fichiers de test, ceux qui se terminent par _test.go, peuvent utiliser l'un de deux noms de package. package billing place le test à l'intérieur du package, ce qui lui permet d'appeler des fonctions non exportées :
example.gogopackage billing import "testing" func TestAddVAT(t *testing.T) { if got := addVAT(1000); got != 1250 { t.Fatalf("addVAT(1000) = %d, want 1250", got) } }
package billing_test en fait un package de test externe. Il se trouve dans le même répertoire, mais il doit importer billing comme n'importe quel autre appelant et ne peut utiliser que les noms exportés :
example.gogopackage billing_test import ( "testing" "example.com/shop/billing" ) func TestTotal(t *testing.T) { got := billing.Total([]int64{1000, 2000}) if got != 3750 { t.Fatalf("Total = %d, want 3750", got) } }
Hormis les fichiers exclus par une contrainte de build, comme un script générateur marqué //go:build ignore, le suffixe _test est le seul moyen pour deux noms de package de cohabiter dans un même répertoire. go test compile les deux packages séparément. Les tests externes vérifient l'API telle que les appelants l'utilisent, et c'est aussi ainsi que la bibliothèque standard écrit les fonctions Example exécutables qui apparaissent dans la documentation. De nombreuses bases de code Go combinent les deux, avec des tests internes au package pour les détails d'implémentation délicats et des packages _test pour le comportement public.
Que peut-on placer avant la clause package ?
Uniquement des commentaires et des lignes vides. Deux types de commentaires, à cet endroit, ont une signification pour la chaîne d'outils.
Un commentaire de documentation du package se place juste au-dessus de package, sans ligne vide entre les deux. Par convention, il commence par Package billing, et go doc et pkg.go.dev l'affichent comme documentation du package. Les packages volumineux le placent souvent dans un fichier doc.go qui ne contient rien d'autre. Pour une commande en package main, le commentaire décrit plutôt le programme, comme dans // Api serves the shop's HTTP API.
Une contrainte de build détermine si le fichier est compilé ou non. Elle doit précéder la clause package et être suivie d'une ligne vide :
example.gogo//go:build debug package billing import "log" func init() { log.Println("billing: debug build") }
Ce fichier n'est compilé qu'avec go build -tags debug. Les fichiers dont le nom porte un suffixe de système d'exploitation ou d'architecture, comme watch_linux.go ou asm_arm64.go, reçoivent le même traitement d'après leur nom, sans aucun commentaire.
L'exemple montre aussi init. Un package peut déclarer autant de fonctions init qu'il le souhaite, y compris plusieurs dans un même fichier. Go les exécute après l'initialisation des variables de niveau package et avant le démarrage de tout ce qui importe le package. Un package importé est entièrement initialisé avant le package qui l'importe, donc main s'exécute toujours en dernier. Réservez init aux initialisations peu coûteuses, comme l'enregistrement d'un driver. Tout ce qui peut échouer a sa place dans une fonction qui retourne une erreur.
La place de LevelUpGo
LevelUpGo enseigne Go au travers 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 d'un package, les modules Go et l'organisation d'un projet, et Code Organization montre comment découper en packages aux noms clairs une base de code qui grandit. 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
Le nom d'un package Go peut-il différer du nom de son répertoire ?
Oui. Le compilateur ne lit que la clause package, donc package httpapi dans un dossier nommé httpapi-v2 se compile sans problème. La convention veut que le nom corresponde au dernier élément du chemin d'import, et les principales exceptions sont les suffixes de version comme gopkg.in/yaml.v3 (package yaml) et math/rand/v2 (package rand).
Pourquoi un programme Go a-t-il besoin de package main ?
La commande go ne produit un exécutable qu'à partir d'un package nommé main, et le runtime démarre le programme en appelant la fonction main de ce package. Tous les autres packages sont des bibliothèques. Un package main sans func main() échoue avec function main is undeclared in the main package.
Un module Go peut-il contenir plusieurs package main ?
Oui, un par répertoire. Un service doté d'un serveur d'API et d'un outil de migration contient généralement cmd/api/main.go et cmd/migrate/main.go, qui déclarent tous deux package main. Vous compilez ou exécutez chacun d'eux via son répertoire : go run ./cmd/api ou go build ./cmd/migrate.
Le nom d'un package Go peut-il contenir des underscores ou des majuscules ?
Le compilateur autorise les deux, car un nom de package peut être n'importe quel identifiant valide sauf _. Le style Go recommande de ne pas les utiliser. L'article Package names du blog Go demande des noms courts, en minuscules, « sans under_scores ni mixedCaps », comme strconv ou httputil.
package est-il un mot-clé en Go ?
Oui. package fait partie des 25 mots-clés réservés de Go, vous ne pouvez donc pas l'utiliser comme nom de variable ou de fonction. Le code Go qui a besoin d'une variable pour désigner un package la nomme généralement pkg.
Sources
- The Go Programming Language Specification, Package clause : https://go.dev/ref/spec#Package_clause
- The Go Programming Language Specification, Program initialization and execution : https://go.dev/ref/spec#Program_initialization_and_execution
- The Go Programming Language Specification, Exported identifiers : https://go.dev/ref/spec#Exported_identifiers
- Effective Go, Package names : https://go.dev/doc/effective_go#package-names
- The Go Blog, Package names : https://go.dev/blog/package-names
- Go Doc Comments, Packages and Commands : https://go.dev/doc/comment#package
- Organizing a Go module : https://go.dev/doc/modules/layout
- Command go, Internal Directories : https://pkg.go.dev/cmd/go#hdr-Internal_Directories
- Command go, Build constraints : https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Command go, Test packages : https://pkg.go.dev/cmd/go#hdr-Test_packages
