Mendeklarasikan method
Method adalah fungsi yang punya receiver: parameter tambahan yang ditulis sebelum nama method. Receiver mengikat method ke sebuah tipe.
Nama receiver biasanya satu atau dua huruf yang diambil dari nama tipenya (c untuk Circle). Go tidak memakai this atau self, dan panduan gaya Go tidak menyarankan nama-nama itu.
Karena Circle punya method String() string, ia memenuhi fmt.Stringer, dan fmt.Println mencetak hasil method itu, bukan {2}.
Value receiver dan pointer receiver
Value receiver (c Circle) mendapat salinan. Pointer receiver (c *Circle) mendapat alamatnya, jadi perubahan pada field tetap tersimpan.
IncByValue bisa dikompilasi tapi tidak melakukan apa pun yang terlihat, sebuah bug klasik. Kalau sebuah method dimaksudkan untuk mengubah receiver-nya, method itu butuh pointer receiver.
Pemanggilan di atas juga menunjukkan dua kemudahan yang diberikan Go. c.Inc() pada variabel yang addressable ditulis ulang menjadi (&c).Inc(), dan p.Value() pada pointer ditulis ulang menjadi (*p).Value(). Kamu jarang perlu menulis & atau * hanya untuk memanggil method.
& otomatis itu butuh sesuatu yang addressable. Elemen map atau composite literal tidak addressable:
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
Simpan pointer di map (map[string]*Counter), atau salin nilainya keluar, ubah, lalu tulis kembali.
Receiver mana yang dipilih
| Pakai pointer receiver saat | Pakai value receiver saat |
|---|---|
| Method mengubah receiver-nya | Tipenya kecil dan tidak pernah diubah oleh method-nya (time.Time, sebuah titik) |
| Struct-nya besar (menyalin itu mahal) | Tipenya berupa map, func, atau chan (sudah berupa referensi) |
Tipenya menyimpan sync.Mutex atau sejenisnya | Kamu ingin nilainya berperilaku seperti tipe primitif, aman untuk disalin |
| Method lain pada tipe itu sudah memakai pointer |
FAQ Go dan standard library mengikuti satu patokan: konsisten. Kalau ada method yang butuh pointer receiver, berikan pointer receiver ke semua method, supaya method set-nya mudah ditebak. go vet menandai struct berisi sync.Mutex yang tersalin lewat value receiver (passes lock by value).
Method set dan interface
Perbedaan ini paling penting untuk interface. Method set milik T berisi method dengan value receiver. Method set milik *T berisi method dengan value receiver maupun pointer receiver.
Menulis Square{Side: 3} tanpa & di slice itu akan gagal dikompilasi:
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)
Alasannya: interface bisa menyimpan salinan Square yang tidak addressable, jadi Go tidak bisa mengambil alamatnya untuk memanggil method pointer. Lihat interface untuk kelanjutan ceritanya.
Method pada tipe yang bukan struct
Named type apa pun yang dideklarasikan di package-mu bisa punya method, bukan hanya struct.
Batasannya: tipe itu harus didefinisikan di package yang sama dengan method-nya, dan underlying type-nya tidak boleh berupa pointer atau interface. Kamu tidak bisa menambahkan method ke int, string, atau time.Time dari package-mu sendiri. Membungkusnya dalam named type baru adalah cara Go. String() pada tipe integer juga merupakan cara enum mendapatkan output yang mudah dibaca; lihat enum dengan iota.
Method value dan method expression
Method yang terikat ke receiver tertentu adalah sebuah nilai fungsi:
Method value dengan value receiver menyalin receiver-nya saat dibuat, itulah sebabnya f masih menyebut Ana. Dengan pointer receiver, ia akan menyimpan pointer dan melihat perubahan yang terjadi kemudian. Method value berguna untuk callback: http.HandleFunc("/", srv.handleIndex).
Kesalahan umum
- Value receiver pada method yang mengubah data. Bisa dikompilasi, tapi diam-diam hanya mengubah salinan.
- Mencampur jenis receiver. Membuat tidak jelas apakah
Tatau*Tyang memenuhi interface. Pilih satu untuk setiap tipe. - Memanggil method pointer pada pointer nil. Diperbolehkan, dan method-nya berjalan dengan receiver nil. Method itu baru panic saat menyentuh sebuah field. Beberapa tipe (seperti linked list yang aman terhadap nil) sengaja memanfaatkan ini; kebanyakan tipe tidak mengantisipasinya.
Pertanyaan yang sering diajukan
Apa perbedaan method dan fungsi di Go?
Method punya receiver, yang ditulis di antara func dan namanya: func (c Circle) Area() float64. Kamu memanggilnya pada sebuah nilai (c.Area()), method itu termasuk method set tipenya, dan dengan method-lah sebuah tipe memenuhi interface. Selain itu, method berperilaku seperti fungsi yang argumen pertamanya adalah receiver.
Kapan saya harus memakai pointer receiver di Go?
Pakai pointer receiver saat method harus mengubah receiver-nya, saat struct-nya cukup besar sehingga menyalinnya di setiap pemanggilan jadi boros, atau saat tipenya berisi sesuatu yang tidak boleh disalin, seperti sync.Mutex. Kalau ada satu method dari sebuah tipe yang butuh pointer receiver, saran yang umum adalah menjadikan semua method-nya pointer receiver agar konsisten.
Kenapa tipe saya tidak mengimplementasikan interface padahal method-nya punya pointer receiver?
Method dengan pointer receiver termasuk method set milik *T, bukan T. Jadi var s Shape = Square{} gagal dengan Square does not implement Shape (method Area has pointer receiver). Assign pointer sebagai gantinya: var s Shape = &Square{}.
Bisakah mendefinisikan method pada tipe bawaan di Go?
Tidak secara langsung. Method hanya bisa dideklarasikan pada named type yang didefinisikan di package yang sama. Definisikan tipemu sendiri dulu, type Celsius float64, lalu tambahkan method ke tipe itu. Kamu tidak bisa menambahkan method ke int, string, atau tipe dari package lain.