Lesson 4/8 10 min

Slices and maps

Slices (dynamic lists) with append, len and slicing, and maps (key/value) with the comma-ok idiom and delete.

FR EN

Array contre slice

Go propose deux façons de stocker une liste de valeurs. L'array a une taille fixée à la déclaration et qui ne bouge plus jamais :

var nombres [3]int    // un array de 3 entiers, taille figée
nombres[0] = 10

En pratique, vous utiliserez le slice 99 % du temps. C'est la version dynamique : il grandit et rétrécit à volonté. On le reconnaît aux crochets vides [] devant le type :

scores := []int{10, 20, 30}   // slice littéral
vide := []int{}               // slice vide
autre := make([]int, 0)       // même chose avec make

La différence clé : [3]int est un array (taille fixe), []int est un slice (taille dynamique). Quand vous hésitez, prenez le slice.

append, len et le slicing

Pour ajouter un élément, on utilise append. Il renvoie un nouveau slice qu'on réaffecte à la variable. len donne la taille :

scores := []int{10, 20, 30}
scores = append(scores, 40)   // [10 20 30 40]
fmt.Println(len(scores))      // 4

Le slicing s[debut:fin] extrait une portion. La borne de début est incluse, celle de fin est exclue :

scores := []int{10, 20, 30, 40}
milieu := scores[1:3]   // [20 30] : index 1 et 2, pas 3

Sortie attendue :

[10 20 30 40]
4
[20 30]

À vous d'essayer :

package main

import "fmt"

func main() {
    scores := []int{10, 20, 30}
    scores = append(scores, 40)
    fmt.Println(scores)
    fmt.Println(len(scores))
    fmt.Println(scores[1:3])
}

Parcourir avec range

Pour parcourir un slice, la boucle for ... range vous donne à chaque tour l'index et la valeur :

fruits := []string{"pomme", "banane", "cerise"}
for i, v := range fruits {
    fmt.Println(i, v)
}

Si l'index ne vous intéresse pas, remplacez-le par _ (le blank identifier). Go vous oblige à le faire, sinon il râle sur la variable inutilisée.

Sortie attendue :

0 pomme
1 banane
2 cerise

À vous d'essayer :

package main

import "fmt"

func main() {
    fruits := []string{"pomme", "banane", "cerise"}
    for i, v := range fruits {
        fmt.Println(i, v)
    }
}

Les maps : clé / valeur

Une map associe des clés à des valeurs, comme un dictionnaire en Python ou un objet en JavaScript. Le type s'écrit map[TypeCle]TypeValeur :

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}
ages["Charlie"] = 42      // écrire
fmt.Println(ages["Alice"]) // lire : 30

Problème : que renvoie ages["Inconnu"] ? Pas une erreur, mais la valeur zéro du type (ici 0). Impossible alors de distinguer "la clé vaut 0" de "la clé n'existe pas". La solution est l'idiome comma-ok :

v, ok := ages["Inconnu"]
if ok {
    fmt.Println("trouvé :", v)
} else {
    fmt.Println("clé absente")
}

Le second retour ok est un booléen : true si la clé existe, false sinon. Pour supprimer une clé, on utilise delete, et on parcourt une map avec range :

delete(ages, "Bob")        // supprime la clé "Bob"
for nom, age := range ages {
    fmt.Println(nom, age)
}

Attention : accéder à une clé absente ne provoque jamais d'erreur en Go : la map renvoie silencieusement la valeur zéro du type (0 pour un int, "" pour une string, nil pour un pointeur). C'est exactement pour ça que l'idiome comma-ok v, ok := m[cle] existe : seul ok vous dit si la clé était vraiment là.

Sortie attendue :

30
clé absente
Alice 30
Charlie 42

À vous d'essayer :

package main

import "fmt"

func main() {
    ages := map[string]int{"Alice": 30, "Bob": 25}
    ages["Charlie"] = 42
    fmt.Println(ages["Alice"])

    if _, ok := ages["Inconnu"]; !ok {
        fmt.Println("clé absente")
    }

    delete(ages, "Bob")
    for nom, age := range ages {
        fmt.Println(nom, age)
    }
}

Array vs slice

Go offers two ways to store a list of values. An array has a size fixed at declaration that never changes again:

var numbers [3]int    // an array of 3 ints, frozen size
numbers[0] = 10

In practice, you'll use the slice 99% of the time. It's the dynamic version: it grows and shrinks at will. You recognize it by the empty brackets [] before the type:

scores := []int{10, 20, 30}   // slice literal
empty := []int{}              // empty slice
other := make([]int, 0)       // same thing with make

The key difference: [3]int is an array (fixed size), []int is a slice (dynamic size). When in doubt, use the slice.

append, len and slicing

To add an element, use append. It returns a new slice that you reassign to the variable. len gives the size:

scores := []int{10, 20, 30}
scores = append(scores, 40)   // [10 20 30 40]
fmt.Println(len(scores))      // 4

Slicing s[start:end] extracts a portion. The start bound is included, the end bound is excluded:

scores := []int{10, 20, 30, 40}
middle := scores[1:3]   // [20 30] — index 1 and 2, not 3

Expected output:

[10 20 30 40]
4
[20 30]

Looping with range

To loop over a slice, the for ... range loop gives you the index and the value on each iteration:

fruits := []string{"apple", "banana", "cherry"}
for i, v := range fruits {
    fmt.Println(i, v)
}

If you don't need the index, replace it with _ (the blank identifier). Go forces you to do this, otherwise it complains about the unused variable.

Expected output:

0 apple
1 banana
2 cherry

Maps: key / value

A map associates keys with values, like a dictionary in Python or an object in JavaScript. The type is written map[KeyType]ValueType:

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}
ages["Charlie"] = 42       // write
fmt.Println(ages["Alice"]) // read: 30

The catch: what does ages["Unknown"] return? Not an error, but the zero value of the type (here 0). So you can't tell "the key equals 0" from "the key doesn't exist". The solution is the comma-ok idiom:

v, ok := ages["Unknown"]
if ok {
    fmt.Println("found:", v)
} else {
    fmt.Println("missing key")
}

The second return value ok is a boolean: true if the key exists, false otherwise. To remove a key, use delete, and loop over a map with range:

delete(ages, "Bob")        // removes the key "Bob"
for name, age := range ages {
    fmt.Println(name, age)
}

Warning: accessing a missing key never raises an error in Go: the map silently returns the zero value of the type (0 for an int, "" for a string, nil for a pointer). This is exactly why the comma-ok idiom v, ok := m[key] exists: only ok tells you whether the key was really there.

Expected output:

30
missing key
Alice 30
Charlie 42
Testez dans le Go Playground

Copiez les exemples ci-dessus et testez-les dans le Go Playground :

Avec l'IA

Copiez ce prompt dans Claude ou ChatGPT :

Écris un programme Go qui compte le nombre d'occurrences de chaque mot dans une phrase, en utilisant une map[string]int. Explique l'idiome comma-ok dans le code.
Ré-explique sans regarder

Sans relire le code de l'IA : avec tes mots, pourquoi l'idiome v, ok := m[cle] existe-t-il, alors que m[cle] tout court fonctionne déjà ?

Une bonne explication dit : m[cle] sur une clé absente ne plante pas, il renvoie la valeur zéro du type (0, "", nil). Du coup une vraie valeur 0 est indistinguable d'une clé manquante. v, ok := m[cle] ajoute un second retour booléen : ok vaut true seulement si la clé existait. C'est ok, pas v, qui répond à la question « la clé est-elle là ? ».
Accepter ou rejeter le code de l'IA

L'IA te propose ce code pour savoir si un utilisateur a un stock enregistré. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.

stock := map[string]int{"pommes": 0, "bananes": 12}

if stock["pommes"] != 0 {
    fmt.Println("On a des pommes en stock")
} else {
    fmt.Println("Plus de pommes... ou jamais enregistrées ?")
}
À rejeter. Le bug est exactement le piège de cette leçon : tester stock["pommes"] != 0 confond « 0 pomme en stock » avec « clé absente ». Ici "pommes" existe et vaut 0, donc le code dit « plus de pommes » alors que la donnée est bien enregistrée. Pire : si la clé n'existait pas, il dirait pareil. La correction est l'idiome comma-ok : v, ok := stock["pommes"], puis on teste ok pour l'existence et v pour la quantité. Ne jamais déduire l'existence d'une clé à partir de sa valeur.
Rappel libre

Sans remonter dans la leçon : qu'est-ce qui distingue [3]int de []int, et que vaut ok dans v, ok := m[cle] quand la clé est absente ?

[3]int est un array : taille fixe, figée à la déclaration. []int (crochets vides) est un slice : taille dynamique, on l'agrandit avec append. En pratique on prend le slice 99 % du temps. Pour la map, quand la clé est absente ok vaut false et v reçoit la valeur zéro du type ; ok est donc le seul moyen fiable de savoir si la clé existait.
Comment déclarer un slice vide d'entiers en Go ?
Que renvoie s[1:3] pour s := []int{10, 20, 30, 40} ?
Que renvoie l'accès à une clé absente d'une map en Go ?
Next step

Your data is neatly organized in slices and maps. Now we manipulate it with functions that return multiple values, including the famous (result, error) pair: you will see why Go skips try/catch and handles errors with if err != nil, plus the defer keyword.

Lesson 5: Functions and errors →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement