Vol. 6 — № 02
The Go Loop · KiosqueNewsstand
Blog  
Un atelier Go · Édition d'ApprentissageA Go Workshop · Learning Edition

Le receveur, valeur ou pointeur The receiver, value or pointer

func (t T) ou func (t *T) ? Le receveur valeur travaille sur une copie et ne mute rien ; le receveur pointeur mute l'original. Le choix décide aussi du jeu de méthodes — et de ce qui satisfait une interface. func (t T) or func (t *T)? A value receiver works on a copy and mutates nothing; a pointer receiver mutates the original. The choice also decides the method set — and what satisfies an interface.

AudienceAudience
Dev qui voit « does not implement, method has pointer receiver » et ne comprend pas pourquoi Dev who sees 'does not implement, method has pointer receiver' and can't see why
Format
Self-paced
ChapitresChapters
5
Date
Oct 2027 Oct 2027
≈ 16 min ●●○○ ReceveurMéthodesPointeur

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

01CadrageFraming3 min

Une méthode est une fonction avec un receveur. Et ce receveur obéit, lui aussi, à la règle : T est copié, *T ne l'est pas.A method is a function with a receiver. And that receiver, too, obeys the rule: T is copied, *T is not.

Le numéro précédent a posé la sémantique de valeur fonction par fonction : passer une struct, c'est en passer une copie, et il faut un pointeur pour muter l'original. Une méthode n'échappe pas à cette règle — elle ne fait que la déplacer dans un endroit particulier de la signature : le receveur, ce petit paramètre placé avant le nom de la méthode. Quand tu écris func (c Counter) Foo(), le receveur c est une valeur, donc une copie de l'appelant, exactement comme un argument passé par valeur. Quand tu écris func (c *Counter) Foo(), le receveur est un pointeur, donc l'adresse de l'appelant, et la méthode travaille sur l'original. Tout ce qu'on a appris au numéro 1 s'applique tel quel : le receveur valeur protège mais ne mute pas ; le receveur pointeur mute mais expose l'original. La nouveauté, c'est que ce choix n'est plus pris au cas par cas, à chaque appel — il est gravé une fois pour toutes dans la déclaration de la méthode. Tu décides, en écrivant la méthode, si elle verra une copie ou l'original, pour tous ses appelants présents et futurs. Et cette décision, anodine en apparence, a une conséquence que le numéro 1 n'avait pas : elle façonne ce qu'on appelle le jeu de méthodes du type, et donc ce qui pourra, ou non, satisfaire une interface. C'est le fil qu'on déroule jusqu'au chapitre 4.The previous issue laid down value semantics function by function: passing a struct passes a copy of it, and you need a pointer to mutate the original. A method doesn't escape this rule — it merely moves it to a particular spot in the signature: the receiver, that little parameter placed before the method's name. When you write func (c Counter) Foo(), the receiver c is a value, hence a copy of the caller, exactly like an argument passed by value. When you write func (c *Counter) Foo(), the receiver is a pointer, hence the caller's address, and the method works on the original. Everything we learned in issue 1 applies as is: the value receiver protects but doesn't mutate; the pointer receiver mutates but exposes the original. What's new is that this choice is no longer made case by case, at each call — it's carved once and for all into the method's declaration. You decide, when writing the method, whether it will see a copy or the original, for all its present and future callers. And this decision, seemingly trivial, has a consequence issue 1 didn't: it shapes what's called the type's method set, and thus what can, or cannot, satisfy an interface. That's the thread we unwind through to chapter 4.

Le même type, deux receveurs, deux intentionsThe same type, two receivers, two intents
type Counter struct{ n int }

// receveur VALEUR : c est une copie de l'appelant
func (c Counter) Value() int { return c.n }   // lit, ne mute rien

// receveur POINTEUR : c est l'adresse de l'appelant
func (c *Counter) Inc()      { c.n++ }         // mute l'original

c := Counter{}
c.Inc()                 // … c.n devient 1 (Go prend &c automatiquement)
fmt.Println(c.Value())  // 1
Le receveur, c'est l'argument du numéro 1 déplacéThe receiver is issue 1's argument, relocated
func (c T)
receveur valeur
c est une copie · lit l'état · ne mute jamais l'original
|
func (c *T)
receveur pointeur
c est l'adresse · mute l'original · partage l'état

Choisir le receveur, c'est répondre une fois à la question du volume : cette méthode travaille-t-elle sur une copie, ou sur l'original ? La réponse vaut pour tous les appels — et, on le verra, décide aussi de qui peut appeler quoi.Choosing the receiver is answering, once, the volume's question: does this method work on a copy, or on the original? The answer holds for all calls — and, as we'll see, also decides who can call what.

Le réflexe du numéroThe issue's reflex

« Cette méthode doit-elle changer l'état du type ? si oui, receveur pointeur ; sinon, valeur — et je reste cohérent sur tout le type. » La règle de cohérence est aussi importante que le choix lui-même : si une seule méthode a besoin d'un pointeur, on met toutes les méthodes du type en pointeur, pour ne pas se retrouver avec un jeu de méthodes bancal. Le chapitre 4 montrera ce que « bancal » coûte, exactement."Does this method need to change the type's state? if yes, pointer receiver; if not, value — and I stay consistent across the whole type." The consistency rule matters as much as the choice itself: if a single method needs a pointer, you make all the type's methods pointer receivers, to avoid ending up with a lopsided method set. Chapter 4 will show what "lopsided" costs, exactly.

Pourquoi c.Inc() marche sans écrire (&c).Inc()Why c.Inc() works without writing (&c).Inc()

Tu remarqueras que, dans l'exemple, on appelle c.Inc() directement, alors qu'Inc a un receveur pointeur et que c est une valeur. Go insère automatiquement le &c pour toi, à condition que c soit adressable — c'est-à-dire qu'il ait une adresse en mémoire, ce qui est le cas d'une variable locale. À l'inverse, l'appel inverse marche aussi : sur un pointeur p, p.Value() équivaut à (*p).Value(). Ce sucre syntaxique masque la mécanique au quotidien et rend le code lisible. Mais il a des limites — certaines valeurs ne sont pas adressables — et c'est précisément là que le jeu de méthodes cesse d'être une abstraction théorique pour devenir une erreur de compilation concrète. On y arrive au chapitre 3.You'll notice that, in the example, we call c.Inc() directly, even though Inc has a pointer receiver and c is a value. Go automatically inserts the &c for you, provided c is addressable — that is, has an address in memory, which a local variable does. Conversely, the reverse call also works: on a pointer p, p.Value() is equivalent to (*p).Value(). This syntactic sugar hides the mechanics day to day and keeps code readable. But it has limits — some values aren't addressable — and that's precisely where the method set stops being a theoretical abstraction and becomes a concrete compile error. We get there in chapter 3.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Go Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Go Loop — every volume, forever — for €5, one-time.

Retour au kiosqueBack to newsstand