Make ou New ? La question éternelle du développeur Go
Vous codez en Go depuis une semaine ou un an, mais vous hésitez encore une seconde avant de choisir entre make() et new() ? C'est normal. La question "Why would I make() or new()?" sur Stack Overflow a cumulé des dizaines de milliers de vues, et elle reste d'actualité même en 2025. Pourquoi ? Parce que derrière une apparente simplicité se cache une différence fondamentale dans la philosophie de gestion de la mémoire en Go.
Penchons-nous une bonne fois pour toutes sur le sujet, pour que vous n'ayez plus à deviner. Nous irons des bases aux cas concrets, mettrons en lumière les pièges et vous donnerons une checklist pour votre travail quotidien. C'est parti !
Réponse courte (pour ceux qui sont pressés)
Si vous devez allouer de la mémoire pour une slice, une map ou un channel — utilisez make(). Dans tous les autres cas — new() ou le littéral &Type{}. Mais le diable, comme toujours, se cache dans les détails.
Qu'est-ce que new() et comment ça fonctionne
La fonction new(T) est une fonction intégrée de Go qui alloue de la mémoire pour le type T, la met à zéro (valeur zéro) et retourne un pointeur vers cette mémoire : *T.
En termes simples : new() vous donne un objet vide mais valide. Vous obtenez un pointeur, mais l'objet lui-même n'est pas encore prêt à fonctionner pleinement s'il nécessite une initialisation de ses structures internes.
package main
import "fmt"
type Person struct { Name string Age int}
func main() { p := new(Person) fmt.Println(p) // Affiche : &{ 0} fmt.Println(p.Name == "") // true}Ici, new(Person) a alloué la mémoire pour la structure, mis les champs à zéro (chaîne vide et 0) et retourné un pointeur. Tout est correct.
Qu'est-ce que make() et son super-pouvoir
make() est également une fonction intégrée, mais elle fonctionne uniquement avec trois types : les slices, les maps et les channels. Pourquoi seulement eux ? Parce que ces types ont une structure interne (un en-tête) qui doit être initialisée, et pas seulement mise à zéro.
make() ne retourne pas un pointeur. Il retourne une valeur initialisée du type souhaité (pas un pointeur !).
// Slice : longueur 5, capacité 10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10
// Map : prête à insérer des élémentsm := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]
// Channel : bufferisé pour 3 messagesch := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1Remarque : si vous essayez d'écrire m := new(map[string]int), vous obtiendrez un pointeur vers une map nil. Insérer un élément dans une telle map provoquera une panique (panic).
Pourquoi new() ne fonctionne-t-il pas pour les slices, maps et channels ?
Prenons l'exemple d'une slice. Une slice en Go est une structure composée de trois champs : un pointeur vers un tableau, la longueur et la capacité. new([]int) allouera la mémoire pour cette structure et la mettra à zéro. Le pointeur sera nil, la longueur et la capacité seront 0. Vous obtiendrez &[] — un pointeur vers une slice nil. Cela ne cassera-t-il pas lors d'une affectation par indice ? Non, cela plantera.
make([]int, 3) fait trois choses :
- Alloue en interne un véritable tableau de 3 éléments (ou plus si une capacité est spécifiée).
- Initialise l'en-tête de la slice (pointer, length, capacity).
- Retourne une slice prête à l'emploi.
Idem pour une map : make(map[string]int) crée une table de hachage et alloue la mémoire pour les buckets. new(map[string]int) ne donne qu'une map nil.
Quand utiliser new() en 2025 ?
La pratique moderne dans la communauté Go est la suivante : new() est rarement utilisé. Le plus souvent, les développeurs préfèrent les littéraux avec prise d'adresse : &Type{}. C'est plus lisible et permet de définir immédiatement les champs.