Tranche vide en Go : comment ne pas se tromper dès le début
\n\nChaque développeur Go se retrouve tôt ou tard confronté à la question : "comment initialiser correctement une tranche vide ?". Une tâche apparemment simple, mais tout n'est pas si évident. Dans la FAQ officielle de Go et sur Stack Overflow, des centaines de commentaires et des milliers de votes sont consacrés à ce sujet. Pourquoi ?
\n\nParce qu'en Go, il existe deux manières fondamentalement différentes de créer une tranche vide, et le choix entre elles affecte les performances, la lisibilité du code et même le comportement lors de la sérialisation. Si vous codez en Go et souhaitez écrire un code propre et efficace sans surprises, cet article est pour vous.
\n\nNous allons analyser :
\n- \n
- La différence entre
var s []intets := make([]int, 0)\n - Quand utiliser une nil-tranche et quand utiliser une tranche vide \n
- Comment cela affecte le JSON, la mémoire et les performances \n
- Ce que disent les gourous de Go et les auteurs de la bibliothèque standard \n
Deux frères jumeaux : nil slice vs empty slice
\n\nEn Go, une tranche est une structure composée de trois champs : un pointeur vers un tableau, la longueur et la capacité. Lorsque nous parlons de tranche vide, nous entendons une tranche de longueur 0. Mais la manière dont nous la créons a son importance.
\n\nMéthode 1 : Déclaration var (nil slice)
\n\nvar s []int // s == nil, len(s) == 0, cap(s) == 0\n\nC'est une nil-tranche. Elle ne pointe vers aucun tableau en mémoire. Cependant, vous pouvez librement appeler append dessus — Go allouera la mémoire automatiquement.
Méthode 2 : Initialisation make (empty slice)
\n\ns := make([]int, 0) // s != nil, len(s) == 0, cap(s) == 0\n\nC'est une tranche vide. Elle pointe déjà vers un tableau zéro (un zerobase spécial dans le runtime Go).
\n\nÀ première vue, la différence est imperceptible. Mais creusons un peu plus.
\n\nAffichage console et JSON — premier piège
\n\nEssayez d'exécuter ce code :
\n\npackage main\n\nimport (\n "encoding/json"\n "fmt"\n)\n\nfunc main() {\n var nilSlice []int\n emptySlice := make([]int, 0)\n\n fmt.Printf("nilSlice: %v, is nil: %v\\n", nilSlice, nilSlice == nil)\n fmt.Printf("emptySlice: %v, is nil: %v\\n", emptySlice, emptySlice == nil)\n\n // Sérialisation JSON\n nilJSON, _ := json.Marshal(nilSlice)\n emptyJSON, _ := json.Marshal(emptySlice)\n\n fmt.Printf("nilSlice JSON: %s\\n", nilJSON)\n fmt.Printf("emptySlice JSON: %s\\n", emptyJSON)\n}\n\nLe résultat va vous surprendre :
\n\nnilSlice: [], is nil: true\nemptySlice: [], is nil: false\nnilSlice JSON: null\nemptySlice JSON: []\n\nVoilà la principale surprise ! La sérialisation JSON se comporte différemment. Si votre API attend [] dans la réponse et que vous transmettez une nil-tranche — le client recevra null. Et cela casse les contrats.
De nombreux clients REST (notamment en JavaScript) vérifient strictement : si un tableau est attendu dans la réponse et que null arrive — une erreur peut survenir ou la logique frontend peut être perturbée.
Performances : y a-t-il une différence ?
\n\nRéponse courte : oui, mais dans 99% des cas, elle est négligeable.
\n\nLorsque vous utilisez make([]int, 0), Go alloue immédiatement la mémoire pour la structure de la tranche (24 octets sur un système 64 bits) et la lie à zerobase. Avec var, l'allocation ne se produit qu'au premier append.
Si vous créez une tranche qui restera probablement vide, la nil-tranche économise une allocation mémoire. Si vous savez avec certitude que vous allez ajouter des éléments — make avec indication de capacité offrira de meilleures performances :
// Si vous connaissez la taille approximative — spécifiez la capacité\ns := make([]int, 0, 100) // capacité 100, longueur 0\nfor i := 0; i < 100; i++ {\n s = append(s, i) // sans réallocations !\n}\n\nMais pour la plupart des tâches applicatives, la différence est