Menu

Metodi in Golang: receiver valore e receiver puntatore spiegati

Un metodo è una funzione con un receiver. Impara a dichiarare i metodi, quando usare un receiver valore o un receiver puntatore, i method set e le interface, e i metodi su tipi che non sono struct.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Dichiarare un metodo

Un metodo è una funzione con un receiver: un parametro in più scritto prima del nome del metodo. Il receiver lega il metodo a un tipo.

Il nome del receiver di solito è una o due lettere prese dal tipo (c per Circle). Go non usa this o self, e le guide di stile sconsigliano quei nomi.

Siccome Circle ha un metodo String() string, soddisfa fmt.Stringer, e fmt.Println stampa il risultato invece di {2}.

Receiver valore e receiver puntatore

Un receiver valore (c Circle) riceve una copia. Un receiver puntatore (c *Circle) riceve l'indirizzo, quindi le modifiche ai campi restano.

IncByValue compila e non fa niente di visibile, un bug classico. Se un metodo deve modificare il suo receiver, ha bisogno di un receiver puntatore.

Le chiamate mostrano anche le due comodità che ti offre Go. c.Inc() su una variabile indirizzabile viene riscritto come (&c).Inc(), e p.Value() su un puntatore viene riscritto come (*p).Value(). Raramente scrivi & o * solo per chiamare un metodo.

La & automatica richiede qualcosa di indirizzabile. Un elemento di una map o un letterale composito non lo sono:

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

Memorizza puntatori nella map (map[string]*Counter) oppure copia fuori il valore, modificalo e riscrivilo.

Quale receiver scegliere

Usa un receiver puntatore quandoUsa un receiver valore quando
Il metodo modifica il receiverIl tipo è piccolo e i suoi metodi non lo modificano mai (time.Time, un punto)
La struct è grande (copiarla costa)Il tipo è una map, una func o un chan (già un riferimento)
Il tipo contiene un sync.Mutex o simileVuoi che il valore si comporti come un primitivo, copiabile in sicurezza
Altri metodi del tipo usano già puntatori

Le FAQ di Go e la libreria standard seguono una regola pratica: sii coerente. Se un qualsiasi metodo ha bisogno di un receiver puntatore, dai a tutti i metodi un receiver puntatore, così il method set è prevedibile. go vet segnala una struct che contiene un sync.Mutex e viene copiata attraverso un receiver valore (passes lock by value).

Method set e interface

La distinzione conta soprattutto per le interface. Il method set di T contiene i metodi con receiver valore. Il method set di *T contiene sia i metodi con receiver valore sia quelli con receiver puntatore.

Scrivere Square{Side: 3} senza la & in quella slice non compila:

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)

Il motivo: un'interface può contenere una copia di uno Square che non è indirizzabile, quindi Go non può prenderne l'indirizzo per chiamare un metodo con receiver puntatore. Vedi interface per il resto della storia.

Metodi su tipi che non sono struct

Qualsiasi tipo con nome dichiarato nel tuo package può avere metodi, non solo le struct.

I limiti: il tipo deve essere definito nello stesso package del metodo, e il suo tipo sottostante non può essere un puntatore o un'interface. Non puoi aggiungere metodi a int, string o time.Time dal tuo package. Avvolgerli in un nuovo tipo con nome è il modo Go. String() su un tipo intero è anche il modo in cui gli enum ottengono un output leggibile; vedi enum con iota.

Method value e method expression

Un metodo legato a un receiver specifico è un valore funzione:

Un method value con receiver valore copia il receiver quando viene creato, ed è per questo che f dice ancora Ana. Con un receiver puntatore memorizzerebbe il puntatore e vedrebbe le modifiche successive. I method value sono comodi per le callback: http.HandleFunc("/", srv.handleIndex).

Errori comuni

  • Receiver valore su un metodo che modifica. Compila, e modifica una copia senza avvisi.
  • Mescolare i tipi di receiver. Rende poco chiaro se sia T o *T a soddisfare un'interface. Scegline uno per tipo.
  • Chiamare un metodo con receiver puntatore su un puntatore nil. È permesso, e il metodo gira con un receiver nil. Va in panic solo quando tocca un campo. Alcuni tipi (come una linked list che gestisce il nil) lo sfruttano di proposito; la maggior parte non se lo aspetta.

Domande frequenti

Qual è la differenza tra un metodo e una funzione in Go?

Un metodo ha un receiver, scritto tra func e il nome: func (c Circle) Area() float64. Lo chiami su un valore (c.Area()), appartiene al method set del tipo ed è il modo in cui un tipo soddisfa le interface. Per il resto si comporta come una funzione il cui primo argomento è il receiver.

Quando dovrei usare un receiver puntatore in Go?

Usa un receiver puntatore quando il metodo deve modificare il receiver, quando la struct è abbastanza grande da rendere uno spreco copiarla a ogni chiamata, o quando il tipo contiene qualcosa che non va copiato, come un sync.Mutex. Se un qualsiasi metodo di un tipo ha bisogno di un receiver puntatore, il consiglio abituale è rendere puntatori tutti i receiver per coerenza.

Perché il mio tipo non implementa l'interface quando il metodo ha un receiver puntatore?

I metodi con receiver puntatore appartengono al method set di *T, non di T. Quindi var s Shape = Square{} fallisce con Square does not implement Shape (method Area has pointer receiver). Assegna invece un puntatore: var s Shape = &Square{}.

Si possono definire metodi sui tipi predefiniti in Go?

Non direttamente. I metodi si possono dichiarare solo su tipi con nome definiti nello stesso package. Definisci prima un tuo tipo, type Celsius float64, e poi aggiungigli i metodi. Non puoi aggiungere metodi a int, string o a un tipo di un altro package.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA