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:

go
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:

bash
go run receiver-demo.go

Output yang Dihasilkan:

text
Budi: 10 poin
Budi: 15 poin
Budi: 20 poin
Budi: 20 poin

Mari kita telaah baris demi baris program di atas:

  1. Baris 10–12 (func (p Pencatat) Label): method value receiver yang hanya membaca field — pekerjaan yang cocok untuk salinan.
  2. Baris 14–16 (func (p Pencatat) TambahSalah): sengaja memakai value receiver untuk mutasi; penambahan p.Skor += n hanya mengubah salinan receiver.
  3. Baris 18–20 (func (p *Pencatat) Tambah): method pointer receiver untuk mutasi yang sama; perubahan menyangkut struct asli.
  4. Baris 25–26 (p.TambahSalah(5)): skor tetap tercetak 10 poin — bukti salinan receiver dibuang setelah method selesai.
  5. Baris 28–29 (p.Tambah(5)): skor naik menjadi 15 poin karena kompiler mengubah pemanggilan menjadi (&p).Tambah(5).
  6. Baris 31–34 (penunjuk := &p): memanggil kedua method lewat pointer; penunjuk.Tambah(5) menaikkan skor ke 20 poin dan penunjuk.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:

go
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)
}
bash
go run error-nonaddressable.go

Output yang Dihasilkan:

text
# command-line-arguments
./error-nonaddressable.go:15:20: cannot call pointer method Naik on Counter

Program 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:

KondisiReceiver yang dipilih
Method harus mengubah field structpointer *T
Struct berukuran besar atau memuat array besarpointer *T agar tidak disalin
Method hanya membaca, struct kecilvalue T
Struct memuat sync.Mutex atau status sinkronisasipointer *T
Salah satu method butuh pointerseluruh 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

error-nonaddressable.go

Unduh berkas
go
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)
}

receiver-demo.go

Unduh berkas
go
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())
}