Deklarowanie metody
Metoda to funkcja z odbiorcą: dodatkowym parametrem zapisanym przed nazwą metody. Odbiorca wiąże metodę z typem.
Nazwa odbiorcy to zwykle jedna lub dwie litery wzięte od nazwy typu (c dla Circle). Go nie używa this ani self, a przewodniki stylu odradzają takie nazwy.
Ponieważ Circle ma metodę String() string, spełnia fmt.Stringer, a fmt.Println wypisuje jej wynik zamiast {2}.
Odbiorcy wartościowi i wskaźnikowi
Odbiorca wartościowy (c Circle) dostaje kopię. Odbiorca wskaźnikowy (c *Circle) dostaje adres, więc zmiany pól zostają zachowane.
IncByValue się kompiluje i nie robi nic widocznego, to klasyczny błąd. Jeśli metoda ma zmieniać swojego odbiorcę, potrzebuje odbiorcy wskaźnikowego.
Wywołania pokazują też dwa udogodnienia, które daje Go. c.Inc() na adresowalnej zmiennej zostaje przepisane na (&c).Inc(), a p.Value() na wskaźniku na (*p).Value(). Rzadko piszesz & albo * tylko po to, żeby wywołać metodę.
Automatyczne & wymaga czegoś adresowalnego. Element mapy ani literał złożony takie nie są:
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
Przechowuj w mapie wskaźniki (map[string]*Counter) albo skopiuj wartość, zmień ją i zapisz z powrotem.
Którego odbiorcy wybrać
| Użyj odbiorcy wskaźnikowego, gdy | Użyj odbiorcy wartościowego, gdy |
|---|---|
| Metoda zmienia odbiorcę | Typ jest mały i jego metody go nie zmieniają (time.Time, punkt) |
| Struktura jest duża (kopiowanie kosztuje) | Typ to mapa, funkcja lub kanał (już jest referencją) |
Typ zawiera sync.Mutex lub coś podobnego | Wartość ma się zachowywać jak typ prosty, bezpieczny do kopiowania |
| Inne metody typu już używają wskaźników |
FAQ Go i biblioteka standardowa trzymają się jednej praktycznej zasady: bądź konsekwentny. Jeśli którakolwiek metoda potrzebuje odbiorcy wskaźnikowego, daj odbiorców wskaźnikowych wszystkim metodom, żeby zbiór metod był przewidywalny. go vet zgłasza strukturę z sync.Mutex, która jest kopiowana przez odbiorcę wartościowego (passes lock by value).
Zbiory metod i interfejsy
To rozróżnienie ma największe znaczenie przy interfejsach. Zbiór metod T zawiera metody z odbiorcą wartościowym. Zbiór metod *T zawiera metody z odbiorcą zarówno wartościowym, jak i wskaźnikowym.
Zapisanie w tym slice'ie Square{Side: 3} bez & się nie kompiluje:
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)
Powód: interfejs może przechowywać kopię Square, która nie jest adresowalna, więc Go nie może pobrać jej adresu, żeby wywołać metodę wskaźnikową. Resztę tej historii opisuje strona o interfejsach.
Metody na typach, które nie są strukturami
Metody może mieć każdy typ nazwany zadeklarowany w twoim pakiecie, nie tylko struktura.
Ograniczenia: typ musi być zdefiniowany w tym samym pakiecie co metoda, a jego typ bazowy nie może być wskaźnikiem ani interfejsem. Ze swojego pakietu nie dodasz metod do int, string ani time.Time. Opakowanie w nowy typ nazwany to sposób Go. String() na typie całkowitym to także sposób na czytelny wynik dla enumów; zobacz enumy z iota.
Wartości metod i wyrażenia metod
Metoda związana z konkretnym odbiorcą jest wartością funkcyjną:
Wartość metody z odbiorcą wartościowym kopiuje odbiorcę w chwili utworzenia, dlatego f nadal mówi Ana. Z odbiorcą wskaźnikowym przechowałaby wskaźnik i widziałaby późniejsze zmiany. Wartości metod przydają się jako callbacki: http.HandleFunc("/", srv.handleIndex).
Częste błędy
- Odbiorca wartościowy w metodzie, która coś zmienia. Kompiluje się i po cichu zmienia kopię.
- Mieszanie rodzajów odbiorców. Nie wiadomo wtedy, czy interfejs spełnia
T, czy*T. Wybierz jeden rodzaj dla każdego typu. - Wywołanie metody wskaźnikowej na wskaźniku nil. Jest dozwolone i metoda wykonuje się z odbiorcą nil. Panika pojawia się dopiero przy dostępie do pola. Niektóre typy (np. lista łączona odporna na nil) korzystają z tego celowo; większość się tego nie spodziewa.
Najczęściej zadawane pytania
Czym różni się metoda od funkcji w Go?
Metoda ma odbiorcę, zapisanego między func a nazwą: func (c Circle) Area() float64. Wywołujesz ją na wartości (c.Area()), należy do zbioru metod typu i to dzięki niej typ spełnia interfejsy. Poza tym zachowuje się jak funkcja, której pierwszym argumentem jest odbiorca.
Kiedy w Go używać odbiorcy wskaźnikowego?
Użyj odbiorcy wskaźnikowego, gdy metoda musi zmieniać odbiorcę, gdy struktura jest na tyle duża, że kopiowanie jej przy każdym wywołaniu to marnotrawstwo, albo gdy typ zawiera coś, czego nie wolno kopiować, np. sync.Mutex. Jeśli którakolwiek metoda typu potrzebuje odbiorcy wskaźnikowego, zwykle zaleca się, żeby dla spójności wszystkie miały odbiorców wskaźnikowych.
Dlaczego mój typ nie implementuje interfejsu, gdy metoda ma odbiorcę wskaźnikowego?
Metody z odbiorcą wskaźnikowym należą do zbioru metod *T, a nie T. Dlatego var s Shape = Square{} kończy się błędem Square does not implement Shape (method Area has pointer receiver). Przypisz zamiast tego wskaźnik: var s Shape = &Square{}.
Czy w Go można definiować metody na typach wbudowanych?
Nie bezpośrednio. Metody można deklarować tylko na typach nazwanych zdefiniowanych w tym samym pakiecie. Najpierw zdefiniuj własny typ, type Celsius float64, a potem dodaj do niego metody. Nie da się dodać metod do int, string ani typu z innego pakietu.