Le mot-clé var déclare une variable. Vous lui donnez un nom et un type, une valeur initiale, ou les deux, comme dans var port int, var host = "localhost" ou var ratio float64 = 0.25. Une variable déclarée sans valeur démarre à la valeur zéro de son type : 0 pour les nombres, "" pour les chaînes, false pour les booléens et nil pour les pointeurs, slices, maps, channels, fonctions et interfaces. Dans les fonctions, la plupart du code Go utilise plutôt la forme courte name := value, mais var est le seul moyen de déclarer une variable au niveau du package (spécification Go).
En bref
var name Typedéclare une variable à sa valeur zéro.var name = valuedéduit le type à partir de la valeur.var name Type = valuefait les deux. Un blocvar ( ... )regroupe plusieurs déclarations.- En Go, toute variable est initialisée. Il n'existe pas de mémoire non initialisée, et des types comme
sync.Mutex,sync.WaitGroupetbytes.Buffersont utilisables dès leur valeur zéro. :=déclare et affecte en une seule étape, mais uniquement dans une fonction. Au niveau du package, il fautvar.:=exige au moins un nouveau nom à gauche. Les noms qui existent déjà dans la même portée sont réutilisés, et ceux d'une portée englobante sont masqués.- Masquer une variable externe avec
:=dans un blocifest un bug courant. Le compilateur l'accepte, et l'analyseurshadowle détecte. - Une variable locale inutilisée provoque une erreur de compilation,
declared and not used. Les variables de package inutilisées compilent. - Les variables de package sont initialisées dans l'ordre de leurs dépendances, avant l'exécution de toute fonction
init(). Les erreurs sentinelles et les expressions régulières compilées sont des variables de package typiques. var _ http.Handler = (*Server)(nil)oblige le compilateur à vérifier qu'un type implémente une interface.
Comment déclarer une variable en Go ?
Il existe trois formes, plus un bloc groupé qui peut les combiner librement :
example.gogopackage main import ( "fmt" "time" ) var ( serviceName = "billing-api" maxRetries int64 = 3 requestCount int ) func main() { var port int // type only: starts at 0 var host = "localhost" // value only: the type is inferred as string var ratio float64 = 0.25 // both var readTimeout, writeTimeout time.Duration fmt.Println(serviceName, maxRetries, requestCount, port, host, ratio, readTimeout, writeTimeout) }
Ce programme affiche billing-api 3 0 0 localhost 0.25 0s 0s.
Avec un type et sans valeur, la variable reçoit la valeur zéro de ce type. Avec une valeur et sans type, la variable prend le type de la valeur, et pour une constante non typée comme "localhost" ou 0.25, il s'agit du type par défaut : string, int, float64, rune, bool ou complex128. Avec les deux, la valeur est convertie dans le type déclaré. C'est ainsi que l'on obtient un int64 ou un float32 à partir d'un simple littéral numérique. var maxRetries int64 = 3 est un int64. var maxRetries = 3 est un int.
Une même déclaration peut lister plusieurs noms. var readTimeout, writeTimeout time.Duration déclare deux variables du même type, toutes deux à 0s. Le bloc groupé var ( ... ) est courant au niveau du package, où il permet de regrouper des paramètres liés. Contrairement à un bloc const, il ne répète pas l'expression de la ligne précédente : chaque nom sans valeur reçoit donc la valeur zéro.
Comme tout autre nom en Go, une variable déclarée au niveau du package est exportée si son nom commence par une majuscule. http.DefaultClient et os.Args sont des variables de package exportées. Le mot-clé package en Go explique comment fonctionnent les noms exportés d'un package à l'autre.
Qu'est-ce que la valeur zéro en Go ?
La valeur zéro est la valeur que contient une variable déclarée sans être initialisée. Go n'a pas de mémoire non initialisée, donc chaque variable possède une valeur définie dès sa création :
| Type | Valeur zéro |
|---|---|
bool | false |
int, float64 et les autres types numériques | 0 |
string | "" (la chaîne vide) |
| pointeurs, slices, maps, channels, fonctions, interfaces | nil |
| tableaux | chaque élément à sa valeur zéro |
| structs | chaque champ à sa valeur zéro |
La même règle s'applique à la mémoire obtenue avec new, aux champs de struct omis dans un littéral composite et aux éléments d'une slice créée avec make. Un struct User décodé depuis un JSON sans champ email se retrouve avec Email == "", et non avec des données aléatoires.
À quoi servent les valeurs zéro en Go ?
Un type bien conçu fonctionne dès sa valeur zéro, sans constructeur, et la bibliothèque standard s'appuie sur ce principe. Un sync.Mutex à sa valeur zéro est un mutex déverrouillé, un sync.WaitGroup est un groupe dont le compteur vaut zéro et un bytes.Buffer est un buffer vide prêt à recevoir des écritures. Un struct composé de tels champs est utilisable dès sa déclaration :
example.gogopackage main import ( "bytes" "fmt" "sync" ) type RequestCounter struct { mu sync.Mutex total int } func (c *RequestCounter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.total++ } func main() { var counter RequestCounter // ready to use, no constructor needed var wg sync.WaitGroup for range 10 { wg.Go(counter.Inc) } wg.Wait() var buf bytes.Buffer fmt.Fprintf(&buf, "handled %d requests", counter.total) fmt.Println(buf.String()) }
Ce programme affiche handled 10 requests. Aucune des trois variables n'a eu besoin d'une fonction New. Effective Go recommande de concevoir vos propres types de la même manière, afin que var x T suffise pour commencer à utiliser un T.
Quelles valeurs zéro ne pouvez-vous pas utiliser tout de suite ?
Les maps et, dans une moindre mesure, les pointeurs et les channels. Une slice nil fonctionne avec append, len et range, donc var tags []string suivi de tags = append(tags, "go") ne pose aucun problème. Une map nil peut être lue mais pas modifiée, et y écrire provoque un panic avec assignment to entry in nil map. Déréférencer un pointeur nil provoque un panic, et envoyer sur un channel nil bloque indéfiniment. var vs make en Go passe en revue ces cas et indique quand utiliser make à la place.
Quelle différence entre var et := en Go ?
:= est la déclaration courte de variable. retries := 3 a le même sens que var retries = 3 : il déclare une nouvelle variable et déduit son type à partir de la valeur. Il évite d'écrire le mot-clé et le type, et c'est le choix habituel dans les fonctions, en particulier pour les valeurs renvoyées par un appel de fonction :
example.gogoresp, err := http.Get(healthURL)
La différence tient à l'endroit où chacun est autorisé. := ne fonctionne que dans le corps d'une fonction. Au niveau du package, chaque déclaration doit commencer par un mot-clé, donc ceci échoue :
example.gogopackage main port := 8080 func main() {}
example.texttext./main.go:3:1: syntax error: non-declaration statement outside function body
var port = 8080 au niveau du package compile.
Dans une fonction, les deux formes compilent, et le choix relève de quelques conventions :
- Utilisez
:=quand vous avez une valeur à affecter, commecfg := loadConfig()oun, err := w.Write(body). - Utilisez
varquand vous voulez délibérément la valeur zéro, commevar buf bytes.Buffer,var wg sync.WaitGroupouvar users []Userpour une slice que vous remplissez avecappend.varindique au lecteur que la valeur zéro est le point de départ. - Utilisez
varavec un type quand le type par défaut d'un littéral ne convient pas.var limit int64 = 1 << 40ouvar threshold float32 = 0.8se lit mieux quelimit := int64(1 << 40). - Utilisez
varquand la variable est affectée plus tard dans différentes branches, commevar status stringsuivi d'unif/elsequi la définit.
Peut-on redéclarer une variable avec := en Go ?
En partie. := exige au moins une nouvelle variable à gauche, et tout nom déjà déclaré dans la même portée est affecté au lieu d'être redéclaré. C'est ce qui permet à une fonction de réutiliser un seul err sur plusieurs appels :
example.gogopackage main import "os" func main() { f, err := os.Open("config.yaml") if err != nil { return } defer f.Close() info, err := f.Stat() // ok: info is new, err is reused _ = info f, err := os.Open("secrets.yaml") _ = f }
example.texttext./main.go:14:9: no new variables on left side of :=
info, err := f.Stat() compile parce que info est nouveau. Sur le dernier :=, f et err existent déjà tous les deux, il n'y a donc rien à déclarer. Écrivez f, err = os.Open("secrets.yaml") avec un simple = pour leur affecter une valeur. var n'a pas cette exception. Écrire var err error deux fois dans la même portée échoue avec err redeclared in this block.
Qu'est-ce que le masquage de variables (shadowing) en Go ?
Le masquage se produit quand une déclaration dans une portée interne réutilise le nom d'une variable d'une portée externe. La variable interne cache la variable externe jusqu'à la fin de la portée interne. Chaque bloc if, for, switch et { } ouvre une nouvelle portée, et := déclare toujours dans la portée courante. Ensemble, ces deux règles rendent ce bug facile à écrire :
example.gogopackage main import ( "fmt" "strconv" ) func listenAddr(rawPort string) (string, error) { port := 8080 if rawPort != "" { port, err := strconv.Atoi(rawPort) if err != nil { return "", fmt.Errorf("invalid PORT %q: %w", rawPort, err) } fmt.Println("using PORT", port) } return fmt.Sprintf(":%d", port), nil } func main() { addr, err := listenAddr("9090") fmt.Println(addr, err) }
Ce programme affiche using PORT 9090 puis :8080 <nil>. Dans le bloc if, err est nouveau, donc := est autorisé, et il déclare aussi un nouveau port. La valeur analysée va dans le port interne, qui disparaît à l'accolade fermante. La fonction renvoie le port externe, qui vaut toujours 8080. Le mot-clé if en Go détaille les règles de portée des instructions if qui rendent ce bug possible.
La correction consiste à affecter la variable externe au lieu d'en déclarer une nouvelle. Déclarez d'abord err, puis utilisez = :
example.gogoif rawPort != "" { var err error port, err = strconv.Atoi(rawPort) if err != nil { return "", fmt.Errorf("invalid PORT %q: %w", rawPort, err) } }
Les vérifications par défaut de go vet ne signalent pas le masquage, car une grande partie du masquage est volontaire, comme err dans un if imbriqué. L'analyseur shadow de l'équipe Go, lui, le signale :
example.texttextgo install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest go vet -vettool=$(which shadow) ./...
example.texttext./main.go:11:3: declaration of "port" shadows declaration at line 9
golangci-lint peut exécuter la même vérification via le linter govet avec shadow activé.
Pourquoi Go affiche-t-il « declared and not used » ?
Le compilateur rejette toute variable locale qui n'est jamais lue. Dans le listing ci-dessus, la ligne d'affichage est la seule raison pour laquelle le port interne compile. Sans elle, le compilateur signale declared and not used: port et pointe directement sur le bug. Affecter une valeur ne compte pas comme une utilisation :
example.gogopackage main import "net/http" func main() { resp, err := http.Get("https://example.com") if err != nil { return } retries := 0 retries = 3 }
example.texttext./main.go:6:2: declared and not used: resp ./main.go:10:2: declared and not used: retries
La règle ne concerne que les variables locales. Une variable de package inutilisée, un paramètre de fonction inutilisé et un résultat nommé inutilisé compilent tous. Quand vous devez conserver une valeur que vous ne lisez pas encore, affectez-la à l'identifiant blanc avec _ = resp, ou ignorez-la complètement avec _, err := http.Get(url).
Comment fonctionnent les variables de package en Go ?
Une variable déclarée en dehors de toute fonction appartient au package. Elle est visible dans tous les fichiers du package, existe pendant toute la durée de vie du programme et est initialisée avant le démarrage de main. Les variables de package sont initialisées selon leurs dépendances, et non dans l'ordre où elles apparaissent dans le code source :
example.gogopackage main import ( "errors" "fmt" ) var ( usersURL = apiBase + "/users" apiBase = apiHost + "/v2" apiHost = "https://api.internal" ) var ErrNotFound = errors.New("user not found") func init() { fmt.Println("init sees", usersURL) } func main() { err := fmt.Errorf("load user 42: %w", ErrNotFound) fmt.Println(usersURL, errors.Is(err, ErrNotFound)) }
Ce programme affiche init sees https://api.internal/v2/users puis https://api.internal/v2/users true. usersURL est déclarée en premier mais dépend de apiBase, qui dépend de apiHost, donc le compilateur les initialise dans l'ordre inverse. Les variables qui ne dépendent pas les unes des autres sont initialisées dans l'ordre de déclaration. Deux variables qui dépendent l'une de l'autre provoquent une erreur, initialization cycle for a.
Une fois que chaque variable de package a sa valeur, les fonctions init() s'exécutent, dans l'ordre où elles apparaissent dans le code source, fichier par fichier. Un package peut avoir plusieurs fonctions init(), même dans un seul fichier. Les packages importés sont entièrement initialisés en premier : au moment où votre init() s'exécute, chaque package que vous importez est prêt. Le mot-clé import en Go explique comment init() interagit avec les imports blancs.
Pourquoi déclarer les erreurs sentinelles avec var ?
Parce que errors.New est un appel de fonction, et que son résultat ne peut pas être une constante. Une erreur sentinelle est une variable de package à laquelle les appelants comparent une erreur avec errors.Is :
example.gogovar ( ErrNotFound = errors.New("user not found") ErrEmailTaken = errors.New("email already registered") )
errors.Is compare des valeurs d'erreur, et chaque appel à errors.New renvoie une valeur distincte, même avec le même texte. C'est pourquoi l'erreur doit être créée une seule fois, au niveau du package, puis partagée. Dans la bibliothèque standard, on trouve par exemple io.EOF, sql.ErrNoRows et http.ErrServerClosed.
Il en va de même pour les autres valeurs coûteuses à construire et qui ne changent plus après le démarrage. var slugPattern = regexp.MustCompile("^[a-z0-9-]+$") compile l'expression régulière une seule fois, au lancement du programme. Elle provoque aussi un panic au démarrage si le motif est invalide, au lieu d'échouer à la première requête.
Faut-il utiliser des variables globales en Go ?
Pour des valeurs figées après le démarrage, comme les erreurs sentinelles, les motifs compilés et les tables de correspondance, oui. Pour un état mutable, en général non. Une variable de package var db *sql.DB ou var cfg Config est partagée par chaque goroutine et chaque test du package. Les écritures concurrentes nécessitent un mutex, les tests ne peuvent pas s'exécuter en parallèle avec des valeurs différentes et rien dans la signature d'une fonction n'indique qu'elle dépend de la variable globale.
L'alternative courante consiste à regrouper les dépendances dans un struct et à le transmettre :
example.gogotype Server struct { db *sql.DB logger *slog.Logger } func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) { // uses s.db and s.logger instead of package-level variables }
Chaque test peut alors construire son propre Server avec sa propre base de données et son propre logger.
Que signifie var _ = en Go ?
Cette forme déclare une variable dont le nom est l'identifiant blanc _. La valeur est évaluée, puis jetée. L'usage le plus courant est une vérification à la compilation qu'un type implémente une interface :
example.gogotype Server struct{} func (s *Server) ServeHTTp(w http.ResponseWriter, r *http.Request) {} var _ http.Handler = (*Server)(nil)
Le nom de la méthode ci-dessus contient une faute de frappe, et la déclaration la transforme en erreur de build :
example.texttext./main.go:9:22: cannot use (*Server)(nil) (value of type *Server) as http.Handler value in variable declaration: *Server does not implement http.Handler (missing method ServeHTTP) have ServeHTTp(http.ResponseWriter, *http.Request) want ServeHTTP(http.ResponseWriter, *http.Request)
Sans cette vérification, la faute de frappe n'apparaîtrait qu'à l'endroit où un *Server est passé comme http.Handler, ce qui peut se trouver dans un autre package, voire nulle part. (*Server)(nil) est un pointeur nil converti en *Server, donc la vérification ne coûte aucune allocation. Utilisez plutôt Server{} quand les méthodes ont des récepteurs par valeur.
L'identifiant blanc fonctionne aussi dans les déclarations à valeurs multiples. var _, offset = time.Now().Zone() ne conserve que le second résultat.
Comment déclarer plusieurs variables à la fois en Go ?
Listez les noms séparés par des virgules, et fournissez une liste de valeurs correspondante ou un appel de fonction qui renvoie autant de valeurs :
example.gogopackage main import ( "fmt" "strings" ) var host, port = "localhost", 8080 func main() { var user, domain, found = strings.Cut("[email protected]", "@") fmt.Println(host, port, user, domain, found) }
Ce programme affiche localhost 8080 ops levelupgo.dev true. Sans type, chaque variable prend le type de sa propre valeur : host est donc un string et port un int. Avec un type, comme var minPort, maxPort int = 1024, 65535, toutes les variables le partagent.
L'affectation fonctionne de la même manière. a, b = b, a échange deux variables, car Go évalue toutes les valeurs de droite avant d'en affecter une seule.
Depuis Go 1.22, les variables de boucle déclarées avec := dans une instruction for sont de nouvelles variables à chaque itération. Une closure ou une goroutine qui capture job dans for _, job := range jobs voit le job de cette itération, et non le dernier. Le mot-clé for en Go montre le bug de goroutines que cela a corrigé et comment vérifier quel comportement un module obtient.
Quand utiliser var ou const en Go ?
Utilisez const pour les valeurs connues au moment où vous écrivez le code et qui ne changent jamais, comme les timeouts, les limites et les noms d'en-têtes. Utilisez var pour tout le reste : les valeurs calculées à l'exécution, les valeurs lues depuis la configuration, et toute slice, map, struct ou pointeur, puisque ceux-ci ne peuvent pas être des constantes. Le mot-clé const en Go couvre les constantes non typées, iota et les enums.
La place de LevelUpGo
LevelUpGo enseigne Go à travers des exercices qui exécutent du vrai code Go dans le navigateur. Go Basics présente var, := et les valeurs zéro dès ses premières leçons, aux côtés des constantes et des types. Go Language Deep Dives propose une leçon sur la portée et le masquage dans laquelle vous repérez et corrigez des variables masquées. Le Training Ground propose de courts exercices indépendants pour pratiquer en dehors d'un cours. Pour les 24 autres mots réservés, consultez Les mots-clés de Go : les 25 expliqués.
Questions fréquentes
var est-il un mot-clé en Go ?
Oui. var 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 type. Il introduit une déclaration de variable, soit une déclaration unique var name Type = value, soit un bloc groupé var ( ... ).
Quelle est la différence entre var et := en Go ?
:= est une forme courte de var qui déduit toujours le type et ne fonctionne que dans une fonction. x := 5 et var x = 5 déclarent tous deux un int de valeur 5. var est obligatoire au niveau du package, et c'est le choix le plus clair quand vous voulez la valeur zéro ou un type différent du type par défaut du littéral. := peut aussi mélanger des noms nouveaux et existants, à condition qu'au moins un nom à gauche soit nouveau.
Quelle est la valeur zéro d'une variable en Go ?
C'est la valeur que contient une variable déclarée sans initialiseur : 0 pour les nombres, "" pour les chaînes, false pour les booléens et nil pour les pointeurs, slices, maps, channels, fonctions et interfaces. Les tableaux et les structs reçoivent la valeur zéro dans chaque élément ou champ. Go n'a pas de variables non initialisées.
Peut-on utiliser := en dehors d'une fonction en Go ?
Non. Au niveau du package, chaque déclaration doit commencer par un mot-clé comme var, const, type ou func, et port := 8080 échoue avec syntax error: non-declaration statement outside function body. Écrivez plutôt var port = 8080.
Pourquoi Go renvoie-t-il l'erreur « declared and not used » ?
Parce qu'une variable locale inutilisée signale souvent un bug, comme une variable masquée ou une vérification d'erreur oubliée, le compilateur Go la rejette. Affecter une valeur ne compte pas comme une utilisation. Lisez la variable, supprimez-la ou affectez-la à l'identifiant blanc avec _ = x. Les variables de package et les paramètres de fonction inutilisés sont autorisés.
Sources
- The Go Programming Language Specification, Variable declarations : https://go.dev/ref/spec#Variable_declarations
- The Go Programming Language Specification, Short variable declarations : https://go.dev/ref/spec#Short_variable_declarations
- The Go Programming Language Specification, The zero value : https://go.dev/ref/spec#The_zero_value
- The Go Programming Language Specification, Package initialization : https://go.dev/ref/spec#Package_initialization
- Effective Go, Variables and redeclaration : https://go.dev/doc/effective_go#redeclaration
- Effective Go, Allocation with new : https://go.dev/doc/effective_go#allocation_new
- Go FAQ, Can I stop these complaints about my unused variable/import? : https://go.dev/doc/faq#unused_variables_and_imports
- Fixing For Loops in Go 1.22 : https://go.dev/blog/loopvar-preview
- Analyseur shadow : https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow
