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.
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
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.
« 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.
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.