Kita sudah bisa memanggil p.Tambah() pada variabel p maupun pointer &p — kompiler menyisipkan & secara otomatis. Fleksibilitas pemanggilan ini menimbulkan pertanyaan berikutnya: method apa saja sebenarnya yang dimiliki tipe T dibanding pointer *T? Jawabannya dirumuskan sebagai method set — himpunan method yang melekat pada setiap bentuk tipe. Aturan ini terasa teknis, tetapi menentukan nilai mana yang boleh dimasukkan ke interface, jadi penguasaannya menjadi prasyarat sebelum melangkah ke section Interface .

Method Set dari T dan *T #

Spesifikasi Go merumuskan dua aturan pendek. Method set dari tipe T hanya berisi method yang receiver-nya T; method set dari pointer *T berisi method yang receiver-nya T maupun *T:

Bentuk tipeMethod set berisi
Kalkulator (nilai)method dengan receiver nilai
*Kalkulator (pointer)method dengan receiver nilai dan pointer

Asal-usul aturan ini masuk akal: pointer selalu bisa mengakses nilai yang ditunjuk, sedangkan nilai biasa tidak menjanjikan alamat tetap — nilai yang disalin ke interface tidak punya alamat yang bisa dipertahankan. Method pointer receiver kemudian tidak diberikan pada method set T.

Interface sebagai Kontrak #

Untuk melihat dampaknya kita perlu interface — tipe yang hanya memuat daftar method. Interface menyimpan nilai konkret beserta method yang dimilikinya; detailnya dibedah pada chapter Konsep Interface . Dua interface kecil berikut dipakai sebagai alat uji:

go
type Pembaca interface {
	Nilai() int
}

type Pengubah interface {
	Tambah(n int)
}

Method Nilai milik Kalkulator memakai receiver nilai, sedangkan Tambah memakai receiver pointer. Dengan kedua interface di atas, method set kedua tipe teruji langsung: nilai Kalkulator hanya memenuhi Pembaca, sementara *Kalkulator memenuhi Pembaca sekaligus Pengubah.

Program Utuh #

Berkas method-set-demo.go membandingkan penyimpanan nilai dan pointer ke dalam interface:

go
package main

import "fmt"

type Kalkulator struct {
	Total int
}

func (k Kalkulator) Nilai() int {
	return k.Total
}

func (k *Kalkulator) Tambah(n int) {
	k.Total += n
}

type Pembaca interface {
	Nilai() int
}

type Pengubah interface {
	Tambah(n int)
}

func main() {
	k := Kalkulator{}
	k.Tambah(7)
	fmt.Println("setelah Tambah:", k.Nilai())

	p := &k
	var pembacaDariNilai Pembaca = k
	var pembacaDariPointer Pembaca = p
	var pengubah Pengubah = p

	pengubah.Tambah(3)
	fmt.Println("pembacaDariNilai:", pembacaDariNilai.Nilai())
	fmt.Println("pembacaDariPointer:", pembacaDariPointer.Nilai())
}

Jalankan program dari direktori chapter:

bash
go run method-set-demo.go

Output yang Dihasilkan:

text
setelah Tambah: 7
pembacaDariNilai: 7
pembacaDariPointer: 10

Mari kita telaah baris demi baris program di atas:

  1. Baris 9–11 (func (k Kalkulator) Nilai): method receiver nilai — anggota method set Kalkulator dan *Kalkulator.
  2. Baris 13–15 (func (k *Kalkulator) Tambah): method receiver pointer — hanya anggota method set *Kalkulator.
  3. Baris 17–23 (dua type ... interface): mendeklarasikan kontrak Pembaca dan Pengubah sebagai alat uji method set.
  4. Baris 27 (k.Tambah(7)): memanggil method pointer receiver pada variabel — sah karena k addressable dan kompiler menyisipkan &k.
  5. Baris 31–33 (tiga assignment): memasukkan nilai k ke Pembaca (sah), pointer p ke Pembaca (sah), dan pointer p ke Pengubah (sah); nilai k ke Pengubah akan gagal.
  6. Baris 35–37 (pengubah.Tambah(3)): mengubah k lewat interface, lalu membaca lewat dua interface — pembacaDariNilai menyimpan salinan k saat assignment (Total masih 7), sedangkan pembacaDariPointer berbagi nilai dengan k sehingga membaca 10.

Saat Nilai T Gagal Memenuhi Interface #

Assignment nilai k langsung ke interface Pengubah ditolak kompiler dengan pesan:

text
cannot use k (variable of struct type Kalkulator) as Pengubah value in variable declaration: Kalkulator does not implement Pengubah (method Tambah has pointer receiver)

Pemanggilan langsung k.Tambah(3) tetap sah, sehingga kegagalan ini sering terasa aneh. Bedanya: pemanggilan langsung menyisipkan &k sekali saat itu juga, sedangkan interface harus menyimpan nilai dalam waktu lama — nilai yang disalin ke dalam interface tidak dijamin tetap addressable, jadi Go memilih aman dan hanya menerima pointer.

Pointer Bisa Di-assign, Nilai Non-Addressable Tidak #

Ketika nilai dimiliki variabel biasa, kita selalu bisa mengambil &k untuk mendapatkan method set lengkap. Batasan muncul kembali pada nilai non-addressable: elemen map m["buku"] tidak bisa diambil alamatnya, jadi elemen tersebut tidak bisa dipanggil method pointer-nya maupun dimasukkan ke interface yang menuntut method pointer. Kombinasi method set dan addressable inilah alasan idiom map[string]*Kalkulator begitu umum: elemen pointer selalu punya method set penuh. Aturan ini juga menjadi alasan komunitas Go menyukai interface bermethod value receiver — kontrak seperti itu dipenuhi nilai maupun pointer; perannya dibedah pada chapter Kecilnya Interface .

Referensi #

Berkas Sumber

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

method-set-demo.go

Unduh berkas
go
package main

import "fmt"

type Kalkulator struct {
	Total int
}

func (k Kalkulator) Nilai() int {
	return k.Total
}

func (k *Kalkulator) Tambah(n int) {
	k.Total += n
}

type Pembaca interface {
	Nilai() int
}

type Pengubah interface {
	Tambah(n int)
}

func main() {
	k := Kalkulator{}
	k.Tambah(7)
	fmt.Println("setelah Tambah:", k.Nilai())

	p := &k
	var pembacaDariNilai Pembaca = k
	var pembacaDariPointer Pembaca = p
	var pengubah Pengubah = p

	pengubah.Tambah(3)
	fmt.Println("pembacaDariNilai:", pembacaDariNilai.Nilai())
	fmt.Println("pembacaDariPointer:", pembacaDariPointer.Nilai())
}