In Go i metodi possono essere dichiarati sia per valore che per puntatore (value/pointer receiver). Questa caratteristica è rimasta dalle prime versioni del linguaggio per un controllo esplicito su chi modificherà i dati originali. Il problema classico è la necessità di una distinzione semantica tra value (copia, non modifica) e pointer (accesso condiviso ai dati e possibilità di modifica).
Problema — è facile commettere un errore dichiarando un metodo con value receiver e non ottenere l'effetto atteso, oppure chiamando un metodo value su una variabile di tipo pointer.
Soluzione — attenersi alle seguenti regole:
Esempio di codice:
type Counter struct { Value int } func (c Counter) IncCopy() { c.Value++ } // value receiver func (c *Counter) IncPointer() { c.Value++ } // pointer receiver c := Counter{} c.IncCopy() // Value rimarrà 0 c.IncPointer() // Value diventa 1
Caratteristiche chiave:
È possibile chiamare un metodo con value receiver su un puntatore, e un metodo pointer su un valore?
Go "sotto il cofano" dereferenzia automaticamente i puntatori o ne prende l'indirizzo, quindi la chiamata è consentita se i tipi sono compatibili. Ma non sempre — con le interfacce non funziona allo stesso modo prevedibile.
var c Counter (&c).IncCopy() // È possibile chiamare il metodo value tramite puntatore c.IncPointer() // È possibile chiamare il metodo pointer, Go prenderà automaticamente l'indirizzo
Cosa succede se una struttura implementa solo metodi pointer, ma viene passata per valore a un'interfaccia?
Un oggetto di questo tipo non implementa l'interfaccia se richiede metodi pointer, quindi è possibile un panic o un errore di compilazione.
type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // errore! Counter per valore non implementa l'interfaccia f(&c) // corretto
La struttura cambierà se viene chiamato un metodo pointer receiver, se viene passata una copia del puntatore?
Sì, anche se viene copiato un puntatore, dietro c'è lo stesso oggetto — il risultato sarà lo stesso.
c := Counter{} p := &c p2 := p p2.IncPointer() // Value aumenterà
Un ingegnere implementa una struttura con metodi value receiver "Update". La struttura viene passata tramite un'interfaccia, ma le modifiche "scompaiono" — poiché si lavora con una copia.
Pro:
Contro:
Accordo esplicito nel team: tutti i metodi che modificano lo stato solo come pointer receiver, le interfacce vengono implementate solo con puntatori, value — per "estensioni" e utility.
Pro:
Contro: