Itération sur map en Go : 5 erreurs fatales et comment les éviter

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Itération sur map en Go : comment éviter les pièges (et ne pas perdre de données)

\n\n

Si vous avez déjà programmé en Go, vous avez sûrement rencontré map — l'une des structures de données les plus puissantes et les plus sournoises. On pourrait penser que c'est simple : on prend range, on parcourt les clés, on obtient les valeurs. Mais le diable se cache dans les détails. La question "Iterating over all the keys of a map" sur Stack Overflow a recueilli des milliers de vues, et ce n'est pas un hasard.

\n\n

Aujourd'hui, nous allons analyser les erreurs courantes lors de l'itération sur une map en Go, que même les développeurs expérimentés commettent. Vous découvrirez pourquoi l'ordre de parcours est une loterie, comment travailler en toute sécurité avec une map dans les goroutines et que faire si la map est soudainement nil. C'est parti !

\n\n

Erreur n°1 : Espérer un ordre (ou "Où sont mes clés ?")

\n\n

Le piège le plus courant est de croire que range retournera les clés dans le même ordre que celui dans lequel vous les avez ajoutées. En Go, ce n'est pas le cas. L'ordre d'itération sur une map est fondamentalement non déterministe.

\n\n

Regardez ce code :

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    m := map[string]int{\n        "apple":  1,\n        "banana": 2,\n        "cherry": 3,\n    }\n\n    for i := 0; i < 3; i++ {\n        fmt.Println("Itération", i+1)\n        for k, v := range m {\n            fmt.Printf("  %s: %d\\n", k, v)\n        }\n    }\n}\n
\n\n

Il y a de fortes chances que chaque exécution donne un ordre différent. Ce n'est pas un bug, c'est une fonctionnalité : Go randomise délibérément l'ordre pour que les développeurs ne s'y habituent pas et ne construisent pas de logique sur une séquence aléatoire.

\n\n

Comment l'éviter ?

\n
    \n
  • Ne vous fiez pas à l'ordre — c'est la règle de base.
  • \n
  • Si l'ordre est important, utilisez un []string pour stocker les clés séparément et triez-les :
  • \n
\n\n
package main\n\nimport (\n    "fmt"\n    "sort"\n)\n\nfunc main() {\n    m := map[string]int{"apple": 1, "banana": 2, "cherry": 3}\n\n    keys := make([]string, 0, len(m))\n    for k := range m {\n        keys = append(keys, k)\n    }\n    sort.Strings(keys)\n\n    for _, k := range keys {\n        fmt.Printf("%s: %d\\n", k, m[k])\n    }\n}\n
\n\n

Conseil : Si vous devez conserver l'ordre d'insertion, envisagez la bibliothèque github.com/elliotchance/orderedmap ou écrivez votre propre wrapper basé sur deux structures (map + slice).

\n\n

Erreur n°2 : Itération avec écriture simultanée (data race)

\n\n

En Go, map n'est pas sécurisé pour l'accès concurrent. Si une goroutine lit (itère) une map tandis qu'une autre y écrit au même moment, vous obtiendrez une fatal error: concurrent map iteration and map write.

\n\n

Exemple d'erreur fatale :

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    m := make(map[int]int)\n\n    go func() {\n        for i := 0; i < 100; i++ {\n            m[i] = i\n        }\n    }()\n\n    go func() {\n        for k, v := range m {\n            fmt.Println(k, v)\n        }\n    }()\n\n    time.Sleep(time.Second)\n}\n
\n\n

Ce code plantera avec une panique. Et pas toujours — la race condition peut se manifester à la 1000ème exécution.

\n\n

Comment l'éviter ?

\n\n

Il existe trois méthodes éprouvées :

\n\n
    \n
  1. sync.Mutex — un mutex classique pour synchroniser l'accès :
  2. \n
\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n)\n\ntype SafeMap struct {\n    mu sync.Mutex\n    m  map[int]int\n}\n\nfunc (s *SafeMap) Set(key, value int) {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    s.m[key] = value\n}\n\nfunc (s *SafeMap) Iterate(f func(key, value int) bool) {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    for k, v := range s.m {\n        if !f(k, v) {\n            break\n        }\n    }\n}\n\nfunc main() {\n    sm := SafeMap{m: make(map[int]int)}\n    sm.Set(1, 10)\n    sm.Set(2, 20)\n\n    sm.Iterate(func(k, v int) b                

Blogs

Book Recommendations