backend / go
Maps en profondeur
Explication
Ce que vous allez apprendre
- Créer et manipuler une map avec
make()et la syntaxe littérale - Utiliser le "comma ok idiom" pour distinguer une clé absente d'une valeur zéro présente
- Comprendre pourquoi l'ordre d'itération d'une map n'est jamais garanti
- Modifier une struct stockée dans une map malgré la contrainte d'inadressabilité
- Utiliser une map comme un set, ou pour regrouper des valeurs par clé
Dans quel contexte ?
Un développeur écrit une fonction qui compte le nombre de visites par page sur un site web, à partir des logs du jour. Il stocke ce comptage dans une map[string]int, incrémente une valeur à chaque ligne de log, puis découvre que le rapport affiché change d'ordre à chaque exécution du programme — alors que les données sont identiques. Ce comportement n'est pas un bug : c'est une caractéristique volontaire des maps Go.
D'abord, une map associe des clés à des valeurs, comme un dictionnaire
make(map[string]int) crée une map vide prête à recevoir des paires clé-valeur. On peut aussi l'initialiser directement avec une syntaxe littérale, comme map[string]int{"Alice": 90, "Bob": 85}.
Accéder à une clé absente ne provoque jamais d'erreur : Go retourne simplement la zero value du type de la valeur (0 pour un int). C'est pratique, mais ça crée une ambiguïté qu'il faut savoir lever.
Une fois cette ambiguïté identifiée, voici comment la résoudre
Le "comma ok idiom" — valeur, existe := maMap[cle] — retourne un second booléen qui indique si la clé était réellement présente. C'est le seul moyen fiable de distinguer "cette clé vaut 0" de "cette clé n'existe pas du tout".
| Situation | Sans comma ok | Avec comma ok |
|---|---|---|
| Clé présente avec valeur 0 | Indiscernable d'une clé absente | age, ok := m["x"] → ok == true |
| Clé absente | Retourne silencieusement la zero value | ok == false |
| Suppression | delete(m, cle) | Retire réellement la paire |
Prérequis
Il faut connaître les slices et les boucles for range (leçon précédente), la syntaxe d'itération sur une map étant très proche.
Il reste une randomisation volontaire à intégrer
Depuis ses débuts, Go randomise délibérément l'ordre d'itération d'une map lors d'un for range. Ce choix évite que du code s'appuie accidentellement sur un ordre qui ne serait de toute façon jamais garanti par la spécification du langage — un ordre stable dans une implémentation aurait fini par devenir une dépendance cachée et fragile.
Piège fréquent
Un affichage ou un test qui dépend de l'ordre d'itération d'une map échouera de façon imprévisible. Pour un ordre déterministe, il faut explicitement extraire les clés dans un slice, les trier avec sort.Strings (ou équivalent), puis itérer sur ce slice trié.
Enfin, un détail syntaxique qui surprend : l'inadressabilité des champs
Une struct stockée comme valeur dans une map n'est pas "adressable" : inventaire["P1"].Prix = 39.9 ne compile pas. Il faut récupérer la struct entière dans une variable, la modifier, puis la réassigner intégralement dans la map — une limitation qui pousse souvent à stocker des pointeurs vers des structs plutôt que les structs elles-mêmes.
Bonne pratique
Une map non initialisée (var m map[string]int, valant nil) se lit sans erreur mais panique à l'écriture. Toujours l'initialiser avec make() avant toute tentative d'écriture, même si tu ne prévois d'y insérer des valeurs que plus tard dans le code.
Maintenant que tu maîtrises les deux collections centrales de Go, il te manque un dernier concept pour comprendre pleinement comment les données circulent entre fonctions : les pointeurs, sujet de la prochaine leçon.
Commandes & code
Maps en profondeur
Table de hachage native : clés-valeurs, non ordonnée, référence (comme les slices).
package main
import (
"fmt"
"sort"
)
func main() {
// Creation avec make ou litteral
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
scores := map[string]int{
"Alice": 90,
"Bob": 85,
}
fmt.Println(ages, scores)
// Acces avec verification d'existence (le "comma ok" idiom)
age, existe := ages["Carla"]
fmt.Println(age, existe) // 0 false : zero value si absent
if age, ok := ages["Alice"]; ok {
fmt.Println("Alice a", age, "ans")
}
// Suppression
delete(ages, "Bob")
// Iteration : ORDRE NON GARANTI (volontairement randomise par Go)
for nom, age := range ages {
fmt.Println(nom, age)
}
// Pour un ordre deterministe : trier les cles explicitement
cles := make([]string, 0, len(scores))
for k := range scores {
cles = append(cles, k)
}
sort.Strings(cles)
for _, k := range cles {
fmt.Println(k, scores[k])
}
// Map avec struct en valeur
type Produit struct {
Nom string
Prix float64
}
inventaire := map[string]Produit{
"P1": {Nom: "Clavier", Prix: 49.9},
}
fmt.Println(inventaire["P1"].Nom)
// inventaire["P1"].Prix = 39.9 // ERREUR de compilation : champ non adressable
p := inventaire["P1"]
p.Prix = 39.9
inventaire["P1"] = p // il faut reassigner la struct entiere
// Map comme set (idiome tres courant en Go)
visites := make(map[string]bool)
visites["/home"] = true
visites["/about"] = true
if visites["/home"] {
fmt.Println("deja visite")
}
// Map de slices : regrouper des valeurs par cle
groupes := make(map[string][]string)
groupes["fruits"] = append(groupes["fruits"], "pomme") // marche meme si absent
groupes["fruits"] = append(groupes["fruits"], "poire")
fmt.Println(groupes)
// nil map : lecture OK (zero value), ecriture = panic
var nilMap map[string]int
fmt.Println(nilMap["x"]) // 0, pas de panic
// nilMap["x"] = 1 // panic: assignment to entry in nil map
}Résumé
- L'itération sur une map n'a aucun ordre garanti : trier les clés si besoin.
v, ok := m[k]distingue "absent" de "valeur zéro présente".- Les champs d'une struct stockée dans une map ne sont pas adressables directement.
- Une map
nilse lit sans erreur mais panique à l'écriture — toujoursmake()avant d'écrire.
Exercices pratiques
Mission : un tableau de bord de visites qui panique et ment
Objectif : Corriger un panic sur map nil et une confusion clé absente/valeur zéro, puis rendre un rapport de statistiques déterministe.
Contexte
Un tableau de bord compte les visites par page à partir des logs du jour dans une map[string]int. Le service crashe parfois au démarrage avec panic: assignment to entry in nil map, et une page jamais visitée s'affiche parfois avec le même score qu'une page visitée exactement 0 fois par un bot bloqué.