Method pada chapter sebelumnya memakai receiver nilai (t Titik). Go menyediakan bentuk kedua: receiver pointer (p *Pencatat). Pilihan di antara keduanya bukan gaya penulisan semata — keduanya berbeda semantik. Value receiver membuat method bekerja pada salinan struct, sedangkan pointer receiver membuat method bekerja pada nilai asli. Pada chapter ini kita uji kedua perilaku itu, lalu pelajari aturan addressable yang menentukan kapan pointer receiver bisa dipanggil otomatis.
Value Receiver Menyalin Struct #
Setiap pemanggilan method dengan value receiver menyalin seluruh struct ke receiver baru. Mutasi apa pun pada receiver hanya mengubah salinan tersebut; nilai asli di pemanggil tidak berubah. Salinan ini juga berlaku untuk struct berukuran besar — struct dengan array ribuan byte disalin utuh pada tiap pemanggilan, sehingga biaya penyalinan menjadi pertimbangan nyata.
Pointer Receiver Mengubah Nilai Asli #
Receiver pointer *Pencatat menyimpan alamat struct. Method yang menerima pointer bisa mengubah field dan perubahan terlihat dari semua variabel yang menunjuk struct yang sama. Panggilan pada variabel biasa tetap ditulis p.Tambah(5) — kompiler otomatis menyisipkan &p karena p adalah variabel yang alamatnya bisa diambil. Sebaliknya, method value receiver juga bisa dipanggil lewat pointer: kompiler menyisipkan dereference (*penunjuk).Label() secara otomatis.
Program Utuh #
Berkas receiver-demo.go menempatkan kedua receiver bersebelahan agar bedanya terlihat:
package main
import "fmt"
type Pencatat struct {
Nama string
Skor int
}
func (p Pencatat) Label() string {
return fmt.Sprintf("%s: %d poin", p.Nama, p.Skor)
}
func (p Pencatat) TambahSalah(n int) {
p.Skor += n
}
func (p *Pencatat) Tambah(n int) {
p.Skor += n
}
func main() {
p := Pencatat{Nama: "Budi", Skor: 10}
p.TambahSalah(5)
fmt.Println(p.Label())
p.Tambah(5)
fmt.Println(p.Label())
penunjuk := &p
penunjuk.Tambah(5)
fmt.Println(p.Label())
fmt.Println(penunjuk.Label())
}Jalankan program dari direktori chapter:
go run receiver-demo.goOutput yang Dihasilkan:
Budi: 10 poin
Budi: 15 poin
Budi: 20 poin
Budi: 20 poinMari kita telaah baris demi baris program di atas:
- Baris 10–12 (
func (p Pencatat) Label): method value receiver yang hanya membaca field — pekerjaan yang cocok untuk salinan. - Baris 14–16 (
func (p Pencatat) TambahSalah): sengaja memakai value receiver untuk mutasi; penambahanp.Skor += nhanya mengubah salinan receiver. - Baris 18–20 (
func (p *Pencatat) Tambah): method pointer receiver untuk mutasi yang sama; perubahan menyangkut struct asli. - Baris 25–26 (
p.TambahSalah(5)): skor tetap tercetak10 poin— bukti salinan receiver dibuang setelah method selesai. - Baris 28–29 (
p.Tambah(5)): skor naik menjadi15 poinkarena kompiler mengubah pemanggilan menjadi(&p).Tambah(5). - Baris 31–34 (
penunjuk := &p): memanggil kedua method lewat pointer;penunjuk.Tambah(5)menaikkan skor ke20 poindanpenunjuk.Label()membaca nilai terbaru lewat dereference otomatis.
Aturan Addressable #
Penyisipan &p otomatis hanya berlaku bila nilainya addressable — variabel biasa selalu addressable, tetapi elemen map dan struct literal tidak. Berkas error-nonaddressable.go membuktikan batas tersebut:
package main
import "fmt"
type Counter struct {
N int
}
func (c *Counter) Naik() {
c.N++
}
func main() {
inventori := map[string]Counter{"buku": {N: 2}}
inventori["buku"].Naik()
fmt.Println(inventori)
}go run error-nonaddressable.goOutput yang Dihasilkan:
# command-line-arguments
./error-nonaddressable.go:15:20: cannot call pointer method Naik on CounterProgram sengaja gagal dikompilasi dengan pesan cannot call pointer method Naik on Counter. Elemen map berubah alamatnya ketika map tumbuh, sehingga Go tidak menjamin elemen map bisa diambil alamatnya; struct literal juga ditolak dengan pesan yang sama. Solusinya dua macam: simpan pointer di dalam map sehingga elemennya berupa map[string]*Counter, atau baca elemen, ubah di variabel sementara, lalu simpan kembali ke map.
Memilih Receiver yang Tepat #
Pilihan receiver mengikuti kebutuhan method dan ukuran struct. Tabel berikut merangkum keputusan yang paling umum:
| Kondisi | Receiver yang dipilih |
|---|---|
| Method harus mengubah field struct | pointer *T |
| Struct berukuran besar atau memuat array besar | pointer *T agar tidak disalin |
| Method hanya membaca, struct kecil | value T |
Struct memuat sync.Mutex atau status sinkronisasi | pointer *T |
| Salah satu method butuh pointer | seluruh method sebaiknya pointer |
Baris terakhir tabel adalah aturan konsistensi: mencampur value dan pointer receiver pada satu tipe membuat pembaca harus menelusuri tiap method untuk tahu mana yang mengubah. Konsekuensi campuran ini juga menyangkut interface — pembahasan yang kita lanjutkan pada chapter Method Set .
Referensi #
- Spesifikasi Go: Method declarations — aturan receiver pada deklarasi method.
- FAQ Go: Methods on values or pointers — panduan resmi memilih receiver.
- Spesifikasi Go: Method sets — dasar aturan method set.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
error-nonaddressable.go
Unduh berkaspackage main
import "fmt"
type Counter struct {
N int
}
func (c *Counter) Naik() {
c.N++
}
func main() {
inventori := map[string]Counter{"buku": {N: 2}}
inventori["buku"].Naik()
fmt.Println(inventori)
}
receiver-demo.go
Unduh berkaspackage main
import "fmt"
type Pencatat struct {
Nama string
Skor int
}
func (p Pencatat) Label() string {
return fmt.Sprintf("%s: %d poin", p.Nama, p.Skor)
}
func (p Pencatat) TambahSalah(n int) {
p.Skor += n
}
func (p *Pencatat) Tambah(n int) {
p.Skor += n
}
func main() {
p := Pencatat{Nama: "Budi", Skor: 10}
p.TambahSalah(5)
fmt.Println(p.Label())
p.Tambah(5)
fmt.Println(p.Label())
penunjuk := &p
penunjuk.Tambah(5)
fmt.Println(p.Label())
fmt.Println(penunjuk.Label())
}