Value receiver vs. pointer receiver : un duel qui ne s'éteint pas
\n\nSi vous avez déjà écrit en Go, vous avez forcément rencontré cette question. Vous ouvrez Stack Overflow — et là, des milliers de vues, des centaines de commentaires, et un débutant sur deux (et pas que) se creuse la tête : "Que dois-je choisir ? Value receiver ou pointer receiver ?"
\n\nDivulgâchage : il n'y a pas de réponse unique valable pour tous les cas. Mais il existe des règles claires qui vous aideront à ne pas vous tromper dans 95% des cas. Et aujourd'hui, nous allons les décortiquer une fois pour toutes.
\n\nDans cet article, je ne vais pas simplement traduire une documentation aride. Nous allons parcourir des cas concrets, examiner les performances, la mutabilité, les interfaces et même la façon dont la communauté Go perçoit les différentes approches. C'est parti !
\n\nQu'est-ce qu'un receiver en Go ?
\n\nPour ceux qui découvrent le langage : un receiver est un paramètre qui lie une fonction à un type. C'est ainsi que les méthodes sont implémentées en Go.
\n\ntype User struct {\n Name string\n Age int\n}\n\n// Value receiver\nfunc (u User) Greet() string {\n return "Bonjour, je suis " + u.Name\n}\n\n// Pointer receiver\nfunc (u *User) Birthday() {\n u.Age++\n}\n\n\nDans le premier cas, nous travaillons avec une copie de la structure. Dans le second, avec un pointeur vers l'original. Cela semble un détail. Mais de ce détail découlent des décisions architecturales entières.
\n\nQuand utiliser le value receiver
\n\nCommençons par l'option la plus simple et la plus sûre.
\n\n1. La méthode ne modifie pas le récepteur
\n\nC'est la règle d'or. Si la méthode se contente de lire les données et ne change rien — utilisez le value receiver. C'est sûr, prévisible et clair.
\n\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nPersonne ne s'attend à ce que le calcul de la surface modifie le rectangle lui-même. Le value receiver est ici le choix idéal.
\n\n2. Les petites structures
\n\nSi votre structure ne contient que quelques champs (comme Rectangle ci-dessus), la copie ne coûte pratiquement rien. De plus, cela peut être plus rapide que de travailler avec un pointeur, car les données se trouvent sur la pile, et non sur le tas.
\n\n3. Sécurité et immutabilité
\n\nParfois, vous voulez délibérément garantir que la méthode ne modifiera pas les données d'origine. Le value receiver vous offre cette garantie au niveau du compilateur.
\n\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nC'est le modèle de programmation fonctionnelle : la méthode retourne une nouvelle copie, sans toucher à l'original. Idéal pour des configurations immuables.
\n\nQuand choisir le pointer receiver
\n\nC'est là que les choses deviennent intéressantes. Le pointer receiver est un outil puissant, mais il faut l'utiliser avec précaution.
\n\n1. La méthode modifie le récepteur
\n\nC'est le cas le plus évident. Si vous écrivez SetName, UpdateStatus ou Increment — vous avez besoin d'un pointeur.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nSans pointeur, vous incrémenteriez une copie, et l'original resterait inchangé. Une erreur classique de débutant.
\n\n2. Les grandes structures
\n\nSi votre structure pèse une centaine d'octets ou plus, la copie à chaque appel de méthode est coûteuse. Surtout si la méthode est appelée dans une boucle.
\n\ntype LargeData struct {\n Buffer [1024]byte\n Meta map[string]interface{}\n}\n\n// Copier une telle structure à chaque appel est une douleur
func (l *LargeData) Process() {
// on travaille avec l'original
}
\n\nIci, le pointer receiver n'est pas qu'une commodité, c'est une optimisation des performances.
\n\n