W Go metody można deklarować jako dla wartości, jak i dla wskaźnika na typ (value/pointer receiver). Ta cecha zachowała się z wczesnych wersji języka, aby uzyskać wyraźną kontrolę nad tym, kto będzie zmieniał dane źródłowe. Klasyczny problem to potrzeba zrozumienia różnicy między semantyką value (kopiowanie, brak zmian) a pointer (wspólny dostęp do danych i możliwość modyfikacji).
Problem — łatwo popełnić błąd, deklarując metodę z value receiver i nie osiągając oczekiwanego efektu, lub wywołując metodę value na zmiennej typu pointer.
Rozwiązanie — trzymać się następujących zasad:
Przykład kodu:
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 pozostanie 0 c.IncPointer() // Value stanie się 1
Kluczowe cechy:
Czy można wywołać metodę value receiver na wskaźniku, i metodę pointer na wartości?
Go "pod maską" automatycznie dereferencuje wskaźniki lub pobiera ich adres, dlatego wywołanie jest dozwolone, jeśli typy są zgodne. Ale nie zawsze — z interfejsami to nie działa tak przewidywalnie.
var c Counter (&c).IncCopy() // Można wywołać metodę value przez wskaźnik c.IncPointer() // Można wywołać metodę pointer, Go automatycznie weźmie adres
Co się stanie, jeśli struktura implementuje tylko metody pointer, ale zostanie przekazana przez wartość do interfejsu?
Taki obiekt nie implementuje interfejsu, jeśli wymaga metod pointer, dlatego możliwy jest panic lub błąd kompilacji.
type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // błąd! Counter po wartości nie implementuje interfejsu f(&c) // poprawne
Czy struktura zmieni się przy wywołaniu metody pointer receiver, jeśli zostanie przekazana kopia wskaźnika?
Tak, nawet jeśli kopiowany jest wskaźnik, pod nim leży ten sam obiekt — wynik będzie taki sam.
c := Counter{} p := &c p2 := p p2.IncPointer() // Value wzrośnie
Inżynier implementuje strukturę z metodami value receiver "Update". Przez interfejs przekazywana jest struktura, ale zmiany "giną" — ponieważ działają na kopii.
Zalety:
Wady:
Jasne porozumienie w zespole: wszystkie metody zmieniające stan tylko pointer receiver, interfejsy implementowane tylko przez wskaźniki, value — dla "rozszerzeń" i narzędzi.
Zalety:
Wady: