Les tranches et les tableaux sont l'une des structures de données les plus utilisées en Go. Malgré une syntaxe similaire, la différence dans leur structure et leur comportement peut entraîner des erreurs de performance, de mémoire et de sémantique.
Historique de la question :
Go a depuis le début choisi un modèle de gestion de la mémoire explicite, dans lequel les tableaux (arrays) sont une séquence d'éléments de taille fixe, tandis que les tranches (slices) sont une vue dynamique sur un tableau. Cette séparation permet de contrôler le coût des opérations et le comportement du code.
Problème :
La principale difficulté réside dans la confusion entre la copie d'un tableau (sémantique de valeur) et la "référence" d'une tranche. Les erreurs surviennent souvent lors du passage de ces types dans des fonctions et de la modification de leurs valeurs, entraînant des effets secondaires inattendus.
Solution :
Les tableaux sont toujours copiés lors du passage par valeur : la fonction reçoit une copie de tout le contenu. En revanche, une tranche est une petite structure (en-tête) qui contient un pointeur vers un tableau, une longueur et une capacité. Les modifications à l'intérieur de la tranche sont visibles de l'extérieur si le contenu du tableau est modifié (mais pas si la tranche elle-même est redirigée vers un nouveau tableau à l'intérieur de la fonction).
Exemple de code :
func updateArray(arr [3]int) { arr[0] = 10 } func updateSlice(slc []int) { slc[0] = 10 } func main() { a := [3]int{1,2,3} b := []int{1,2,3} updateArray(a) updateSlice(b) fmt.Println(a) // [1 2 3] fmt.Println(b) // [10 2 3] }
Caractéristiques clés :
Que se passe-t-il si la longueur de la tranche est modifiée à l'intérieur d'une fonction ? Cela affectera-t-il la tranche d'origine ?
Non, la modification de la longueur de la tranche (par exemple, avec slc = slc[:2]) à l'intérieur de la fonction n'affectera que la copie locale de l'en-tête. La tranche d'origine restera inchangée.
L'opérateur append renvoie-t-il la tranche modifiée dans la même zone mémoire ?
Pas nécessairement. Si la capacité n'est pas suffisante, un nouveau tableau est créé et un pointeur vers le nouveau tableau est renvoyé. L'ancien tableau restera intact.
Exemple de code :
s := []int{1,2,3} s2 := append(s, 4, 5, 6) // s2 peut se trouver dans une nouvelle zone mémoire
Peut-on assigner un tableau à une tranche ou vice versa ?
Non. []int et [5]int sont des types différents. Pour passer un tableau en tant que tranche, il faut utiliser la conversion arr[:]. L'inverse n'est pas possible.
Un développeur junior a mis en œuvre une fonction de mise à jour de tableau, en passant un tableau à une fonction en s'attendant à ce que les modifications soient appliquées au tableau d'origine. Les modifications n'ont pas été "sauvegardées".
Avantages :
Inconvénients :
La fonction prenait une tranche et retournait explicitement une copie modifiée, augmentant la prévisibilité de l'effet. Tous les changements étaient conscients, les données n'étaient pas "fuyantes" et ne changeaient pas de manière implicite.
Avantages :
Inconvénients :