Déclarer une méthode
Une méthode est une fonction avec un receveur : un paramètre supplémentaire écrit avant le nom de la méthode. Le receveur rattache la méthode à un type.
Le nom du receveur est généralement une ou deux lettres tirées du type (c pour Circle). Go n'utilise pas this ni self, et les guides de style déconseillent ces noms.
Comme Circle a une méthode String() string, il satisfait fmt.Stringer, et fmt.Println affiche le résultat au lieu de {2}.
Receveurs valeur et receveurs pointeur
Un receveur valeur (c Circle) reçoit une copie. Un receveur pointeur (c *Circle) reçoit l'adresse, donc les modifications des champs sont conservées.
IncByValue compile et ne fait rien de visible, un bug classique. Si une méthode doit modifier son receveur, il lui faut un receveur pointeur.
Les appels montrent aussi les deux commodités que Go vous offre. c.Inc() sur une variable adressable est réécrit en (&c).Inc(), et p.Value() sur un pointeur est réécrit en (*p).Value(). Vous écrivez rarement & ou * juste pour appeler une méthode.
Le & automatique a besoin de quelque chose d'adressable. Un élément de map ou un littéral composite ne l'est pas :
m := map[string]Counter{"a": {}}
m["a"].Inc() // compile error: cannot call pointer method Inc on Counter
Counter{}.Inc() // same error: a composite literal is not addressable
Stockez des pointeurs dans la map (map[string]*Counter) ou copiez la valeur, modifiez-la et réécrivez-la.
Quel receveur choisir
| Utilisez un receveur pointeur quand | Utilisez un receveur valeur quand |
|---|---|
| La méthode modifie le receveur | Le type est petit et jamais modifié par ses méthodes (time.Time, un point) |
| La struct est grosse (la copie coûte) | Le type est une map, une func ou un chan (déjà une référence) |
Le type contient un sync.Mutex ou équivalent | Vous voulez que la valeur se comporte comme un type primitif, copiable sans risque |
| Les autres méthodes du type utilisent déjà des pointeurs |
La FAQ de Go et la bibliothèque standard suivent une règle simple : soyez cohérent. Si une méthode a besoin d'un receveur pointeur, donnez des receveurs pointeurs à toutes les méthodes, pour que l'ensemble de méthodes soit prévisible. go vet signale une struct contenant un sync.Mutex qui est copiée via un receveur valeur (passes lock by value).
Ensembles de méthodes et interfaces
La distinction compte surtout pour les interfaces. L'ensemble de méthodes de T contient les méthodes à receveur valeur. L'ensemble de méthodes de *T contient les méthodes à receveur valeur et à receveur pointeur.
Écrire Square{Side: 3} sans le & dans cette slice ne compile pas :
cannot use Square{…} (value of struct type Square) as Shape value in array or slice literal: Square does not implement Shape (method Area has pointer receiver)
La raison : une interface peut contenir une copie d'un Square qui n'est pas adressable, donc Go ne peut pas prendre son adresse pour appeler une méthode à receveur pointeur. Voir les interfaces pour la suite de l'histoire.
Méthodes sur des types qui ne sont pas des structs
Tout type nommé déclaré dans votre package peut avoir des méthodes, pas seulement les structs.
Les limites : le type doit être défini dans le même package que la méthode, et son type sous-jacent ne peut pas être un pointeur ou une interface. Vous ne pouvez pas ajouter de méthodes à int, string ou time.Time depuis votre propre package. Envelopper dans un nouveau type nommé est la façon de faire en Go. String() sur un type entier est aussi la façon dont les enums obtiennent un affichage lisible ; voir les enums avec iota.
Method values et method expressions
Une méthode liée à un receveur précis est une valeur fonction :
Une method value avec un receveur valeur copie le receveur au moment de sa création, c'est pourquoi f dit toujours Ana. Avec un receveur pointeur, elle stockerait le pointeur et verrait les modifications ultérieures. Les method values sont pratiques pour les callbacks : http.HandleFunc("/", srv.handleIndex).
Erreurs courantes
- Un receveur valeur sur une méthode qui modifie. Ça compile, et ça modifie une copie sans bruit.
- Mélanger les sortes de receveurs. On ne sait plus si c'est
Tou*Tqui satisfait une interface. Choisissez-en une par type. - Appeler une méthode à receveur pointeur sur un pointeur nil. C'est permis, et la méthode s'exécute avec un receveur nil. Elle ne provoque un panic que lorsqu'elle touche un champ. Certains types (comme une liste chaînée tolérante au nil) l'exploitent exprès ; la plupart ne s'y attendent pas.
Questions fréquentes
Quelle est la différence entre une méthode et une fonction en Go ?
Une méthode a un receveur, écrit entre func et le nom : func (c Circle) Area() float64. Vous l'appelez sur une valeur (c.Area()), elle appartient à l'ensemble de méthodes du type, et c'est ainsi qu'un type satisfait des interfaces. Pour le reste, elle se comporte comme une fonction dont le premier argument est le receveur.
Quand utiliser un receveur pointeur en Go ?
Utilisez un receveur pointeur quand la méthode doit modifier le receveur, quand la struct est assez grosse pour que la copier à chaque appel soit du gaspillage, ou quand le type contient quelque chose qui ne doit pas être copié, comme un sync.Mutex. Si une méthode d'un type a besoin d'un receveur pointeur, le conseil habituel est de donner des receveurs pointeurs à toutes, par cohérence.
Pourquoi mon type n'implémente-t-il pas l'interface quand la méthode a un receveur pointeur ?
Les méthodes à receveur pointeur appartiennent à l'ensemble de méthodes de *T, pas de T. Donc var s Shape = Square{} échoue avec Square does not implement Shape (method Area has pointer receiver). Affectez plutôt un pointeur : var s Shape = &Square{}.
Peut-on définir des méthodes sur les types intégrés en Go ?
Pas directement. Les méthodes ne peuvent être déclarées que sur des types nommés définis dans le même package. Définissez d'abord votre propre type, type Celsius float64, puis ajoutez-lui des méthodes. Vous ne pouvez pas ajouter de méthodes à int, à string, ou à un type d'un autre package.