Go mengirimkan argumen fungsi dengan semantik salinan: fungsi menerima salinan nilai, sehingga perubahan pada parameter tidak pernah terlihat oleh pemanggil. Perilaku ini aman, tetapi fungsi tertentu memang dirancang mengubah data milik pemanggil — penghitung skor, pengumpul hasil, atau pengisi konfigurasi. Solusi idiomatiknya adalah mengirim alamat variabel sebagai argumen bertipe pointer. Chapter ini mendemonstrasikan perbedaan kedua bentuk parameter tersebut secara nyata, lalu menjelaskan posisi khusus slice yang sudah mengandung pointer di dalamnya.
Argumen Fungsi Selalu Berupa Salinan #
Kata kuncinya adalah apa yang disalin. Parameter x int membuat salinan nilai angka, sedangkan parameter x *int membuat salinan alamat. Salinan alamat tetap menunjuk variabel yang sama di memori, sehingga fungsi dapat membaca dan menulis variabel asli melalui dereference *x. Tabel berikut merangkum perilaku ketiga bentuk parameter yang sering dibandingkan:
| Bentuk parameter | Yang disalin | Perubahan terlihat pemanggil? |
|---|---|---|
x int | nilai angka | tidak |
x *int | satu alamat | ya, melalui *x |
s []int | header slice (pointer, len, cap) | elemen ya; isi len/cap tidak |
Contoh Utuh: salin versus naikkan #
Program berikut membandingkan dua fungsi dengan tugas yang sama: menaikkan angka sebesar sepuluh. Fungsi salin menerima nilai int, sedangkan fungsi naikkan menerima pointer *int. Program juga memakai new(int) untuk membuat objek int baru tanpa variabel sumber:
package main
import "fmt"
// salin menerima nilai int. Perubahan hanya mengenai salinan parameter.
func salin(x int) {
x = x + 10
}
// naikkan menerima pointer int dan menulis langsung ke variabel pemanggil.
func naikkan(x *int) {
*x = *x + 10
}
func main() {
nilai := 5
salin(nilai)
fmt.Println("setelah salin(nilai) :", nilai) // tetap 5
naikkan(&nilai)
fmt.Println("setelah naikkan(&nilai):", nilai) // menjadi 15
// new(int) mengalokasikan int baru bernilai 0 lalu mengembalikan alamatnya
p := new(int)
naikkan(p)
fmt.Println("nilai *p :", *p) // 10
fmt.Println("p == &nilai :", p == &nilai) // dua objek berbeda
}Jalankan program tersebut dari direktori chapter dengan perintah berikut:
go run pointer-parameter-demo.goOutput yang Dihasilkan:
setelah salin(nilai) : 5
setelah naikkan(&nilai): 15
nilai *p : 10
p == &nilai : falseMari kita telaah baris demi baris program di atas:
- Baris 6-8 (
salin): parameterxadalah salinan nilai5; kenaikanx = x + 10berakhir bersama fungsi, sehingga variabel pemanggil tidak berubah. - Baris 11-13 (
naikkan): parameterxbertipe*int; pernyataan*x = *x + 10membaca nilai di alamat tersebut, menambah sepuluh, lalu menulis hasilnya kembali ke alamat yang sama. - Baris 18-19 (
salin(nilai)): hasil5membuktikan pemanggilan ini tidak menyentuh variabelnilai. - Baris 21-22 (
naikkan(&nilai)): pemanggilan ini mengirim alamat, sehingganilaiberubah menjadi15. - Baris 25-27 (
new(int)): builtinnewmengalokasikan satu nilaiintbernilai0dan mengembalikan alamatnya; setelah dinaikkan,*pbernilai10. - Baris 29 (
p == &nilai): hasilfalsemembuktikanpdan&nilaimenunjuk dua objekintyang berbeda.
new(int) dan Pointer Tanpa Variabel Sumber #
Builtin new(T) mengalokasikan satu nilai bertipe T dengan zero value-nya, lalu mengembalikan pointer *T. Kebalikan dari &x, builtin new tidak membutuhkan variabel yang sudah ada — berguna saat nilai akan dibuat dan dikembalikan sebagai pointer, misalnya hasil kalkulasi yang disimpan di luar fungsi. Untuk kasus sehari-hari seperti contoh di atas, x := 0; p := &x sama kuatnya; new lebih sering muncul pada tipe struct dan array. Operator pembuat instance lain, make, khusus untuk slice, map, dan channel karena ketiga tipe tersebut membutuhkan inisialisasi internal sebelum siap dipakai.
Slice Sudah Mengandung Pointer #
Slice tidak perlu dikirim sebagai pointer agar elemennya dapat diubah. Sebuah slice adalah header berisi pointer ke backing array, panjang, dan kapasitas; salinan header tetap menunjuk backing array yang sama. Fungsi yang menerima []int karenanya dapat mengubah elemen slice milik pemanggil secara langsung. Perubahan pada header itu sendiri — misalnya hasil append yang membuat len bertambah — justru tidak terlihat pemanggil karena header-nya disalin. Detail lengkap header dan backing array tersedia pada chapter Backing Array
.
Referensi #
- Effective Go — pola fungsi dan pemilihan bentuk argumen.
- Go Blog: Arrays, slices and strings — anatomi header slice yang mengandung pointer.
- Spesifikasi bahasa Go: Assignments — aturan penugasan dan dereference.
Parameter pointer bekerja pada method dengan cara yang sama persis, dengan nama khusus pointer receiver. Chapter berikutnya menunjukkan bagaimana pointer receiver membuat method mampu memodifikasi state struct secara langsung.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pointer-parameter-demo.go
Unduh berkaspackage main
import "fmt"
// salin menerima nilai int. Perubahan hanya mengenai salinan parameter.
func salin(x int) {
x = x + 10
}
// naikkan menerima pointer int dan menulis langsung ke variabel pemanggil.
func naikkan(x *int) {
*x = *x + 10
}
func main() {
nilai := 5
salin(nilai)
fmt.Println("setelah salin(nilai) :", nilai) // tetap 5
naikkan(&nilai)
fmt.Println("setelah naikkan(&nilai):", nilai) // menjadi 15
// new(int) mengalokasikan int baru bernilai 0 lalu mengembalikan alamatnya
p := new(int)
naikkan(p)
fmt.Println("nilai *p :", *p) // 10
fmt.Println("p == &nilai :", p == &nilai) // dua objek berbeda
}