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
Un slice obtenu par s[i:j] partage la mémoire du slice d'origine : ce n'est pas une copie. Modifier milieu[0] modifierait aussi scores. De même, append peut soit réutiliser ce tableau sous-jacent (et écraser des données voisines), soit en allouer un nouveau selon la capacité restante. Quand vous avez besoin d'une copie vraiment indépendante, utilisez copy(dst, src) ou slices.Clone(s).
append remplit d'abord la capacité libre, puis réalloue un nouveau tableau quand cap est atteinte.À 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.
À 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 :
Avant de dérouler : var notes map[string]int déclare une map sans l'initialiser. Que se passe-t-il à la ligne notes["Alice"] = 15 : ça marche, ou ça plante ? Et si on LIT une clé absente, par exemple notes["Bob"] : erreur, ou autre chose ?
Voir la réponse
Écrire dans une map NIL provoque une panique à l'exécution (panic: assignment to entry in nil map). Une map déclarée avec var notes map[string]int vaut nil : on peut la LIRE, mais pas y ÉCRIRE. Il faut d'abord l'initialiser avec make(map[string]int) (ou un littéral map[string]int{}). En revanche, LIRE une clé absente ne panique pas : ça renvoie la valeur zéro du type (ici 0 pour un int). Pour distinguer « clé absente » de « clé présente avec la valeur 0 », on utilise la forme à deux retours : v, ok := notes["Bob"] où ok vaut false si la clé n'existe pas. À retenir : toujours make une map avant d'y écrire ; et utiliser , ok pour tester la présence.
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)
}
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à.
À 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
A slice from s[i:j] shares the memory of the original slice: it's not a copy. Modifying middle[0] would also modify scores. Likewise, append may either reuse that backing array (overwriting neighboring data) or allocate a new one depending on the remaining capacity. When you need a truly independent copy, use copy(dst, src) or slices.Clone(s).
append first fills the spare capacity, then reallocates a new array when cap is reached.Try it yourself:
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])
}
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.
Try it yourself:
package main
import "fmt"
func main() {
fruits := []string{"apple", "banana", "cherry"}
for i, v := range fruits {
fmt.Println(i, v)
}
}
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:
Before you scroll down: var notes map[string]int declares a map without initialising it. What happens at the line notes["Alice"] = 15: does it work, or does it crash? And if you READ a missing key, e.g. notes["Bob"]: an error, or something else?
See the answer
Writing to a nil map causes a panic at runtime (panic: assignment to entry in nil map). A map declared with var notes map[string]int equals nil: you can READ it, but not WRITE to it. You must first initialise it with make(map[string]int) (or a literal map[string]int{}). Reading a missing key, on the other hand, does not panic: it returns the zero value of the type (here 0 for an int). To tell apart "missing key" from "key present with value 0", use the two-return form: v, ok := notes["Bob"], where ok is false when the key does not exist. Remember: always make a map before writing to it, and use , ok to test for presence.
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)
}
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.
Try it yourself:
package main
import "fmt"
func main() {
ages := map[string]int{"Alice": 30, "Bob": 25}
ages["Charlie"] = 42
fmt.Println(ages["Alice"])
if _, ok := ages["Unknown"]; !ok {
fmt.Println("missing key")
}
delete(ages, "Bob")
for name, age := range ages {
fmt.Println(name, age)
}
}
🎯 Pratique
S'entraîner (clique pour ouvrir) :
✨ Prompt 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à ?
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 impossible à distinguer 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à ? ».⚖️ Juge 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 ?")
}
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.🔧 Débugue le code
Symptôme : ce code devrait afficher la map avec pommes:5, mais il panique à l'exécution avec assignment to entry in nil map. Répare-le, puis exécute.
package main
import "fmt"
func main() {
var stock map[string]int
stock["pommes"] = 5 // FIXME: nil map
fmt.Println(stock)
}
var stock map[string]int déclare une map à nil. On peut la lire, mais pas y écrire : toute insertion panique. La correction : initialiser avant d'écrire avec stock := make(map[string]int). Règle d'or : toujours make une map avant d'y insérer des clés.Vos données sont bien rangées dans des slices et des maps. On va maintenant les manipuler avec des fonctions qui renvoient plusieurs valeurs, dont la fameuse paire (résultat, error) : vous découvrez pourquoi Go ignore try/catch et gère ses erreurs avec if err != nil, plus le mot-clé defer.
Leçon 5 : Fonctions et erreurs →