Chapter sebelumnya menutup dengan teka-teki: bagaimana fungsi keluaran makeMultiplier masih bisa memakai factor padahal makeMultiplier sudah selesai dieksekusi dan variabel lokalnya seharusnya sudah mati. Jawabannya adalah closure — nilai fungsi yang bersama-sama menangkap variabel lingkungan tempat fungsi tersebut dibuat. Closure membuat fungsi Go berkeadaan (stateful) tanpa struct dan tanpa variabel global. Pada chapter ini kita akan membuktikan mekanisme penangkapan itu dengan tiga eksperimen yang semuanya deterministik.
Apa Itu Closure #
Spec Go mendefinisikan function literal sebagai closure: referensi ke variabel lingkungan yang disimpan bersama fungsi. Masa hidup variabel yang ditangkap mengikuti masa hidup closure-nya, bukan masa hidup fungsi pembuatnya — garbage collector tidak membebaskan variabel selama masih ada closure yang memegangnya. Variabel ditangkap lewat referensi, bukan salinan nilai pada saat pembuatan; setiap pemanggilan closure membaca dan menulis variabel yang sama itu.
Counter Klasik #
Contoh kanoniknya adalah pembangkit counter. Fungsi makeCounter membuat variabel hitungan lalu mengembalikan fungsi tanpa nama yang menaikkan variabel tersebut:
// makeCounter mengembalikan closure yang menaikkan hitungannya sendiri.
func makeCounter() func() int {
hitungan := 0
return func() int {
hitungan++
return hitungan
}
}Tiga pemanggilan next() menghasilkan 1, 2, 3 — hitungan terus hidup di antara pemanggilan karena closure next menahannya. Pemanggilan makeCounter() kedua membuat hitungan yang benar-benar baru, sehingga pemanggilLain() kembali dari 1. Setiap hasil makeCounter adalah unit keadaan terpisah: inilah cara closure menggantikan “objek berstatus” tanpa deklarasi tambahan apa pun.
Dua Closure Berbagi Variabel #
Penangkapan lewat referensi berarti dua closure yang lahir di lingkungan sama membagi satu variabel. Pada program utuh, naik dan turun sama-sama menutup variabel skor milik main:
skor := 0
naik := func() {
skor += 10
}
turun := func() {
skor -= 5
}Urutan naik(), naik(), turun() menghasilkan skor = 15 — ketiga nilai berinteraksi lewat variabel yang sama, bukan salinan masing-masing. Pola setter/getter berpasangan seperti ini adalah cikal bakal enkapsulasi state di Go, akan kita lihat lagi dalam bentuk method pada section Struct dan Method.
Loop Variable Sejak Go 1.22 #
Closure yang dibuat di dalam loop menangkap variabel loop. Sejak Go 1.22, setiap iterasi loop for mendapat variabel i sendiri, sehingga tiga closure di bawah masing-masing menangkap nilai berbeda:
// Setiap iterasi loop mendapat variabel i sendiri (Go 1.22+).
fungsi := make([]func() int, 0, 3)
for i := 1; i <= 3; i++ {
fungsi = append(fungsi, func() int { return i * 10 })
}Program Utuh #
Berkas closure-demo.go menjalankan ketiga eksperimen dalam satu program. Jalankan dari direktori chapter:
go run closure-demo.goOutput yang Dihasilkan:
1
2
3
1
skor = 15
10
20
30Mari kita telaah baris demi baris program di atas:
- Baris 6 (
func makeCounter() func() int): tipe kembalian berupa fungsi — setiap pemanggilan menghasilkan closure baru. - Baris 7 (
hitungan := 0): variabel lokal yang akan ditangkap closure; masa hidupnya diperpanjang oleh closure yang memegang referensinya. - Baris 8–11 (
return func() int { ... }): function literal yang menaikkan lalu mengembalikanhitungan— fungsi dan variabel yang ditangkapnya menjadi satu paket nilai. - Baris 24–30 (
naikdanturun): dua closure menutup variabelskoryang sama milikmain, terbukti dari hasil akhir15. - Baris 37–40 (loop + append): tiga closure dibuat di dalam loop; semantik Go 1.22 memberi masing-masing
iterpisah sehingga hasilnya10,20,30.
Counter, setter berpasangan, dan pembangkit fungsi — semuanya berdiri di satu mekanisme sederhana: fungsi yang membawa lingkungannya. Mekanisme berulang berikutnya berpindah dari variabel ke fungsi itu sendiri: sebuah fungsi yang memanggil dirinya sendiri, atau recursion.
Referensi #
- Function declarations — Go Spec — definisi resmi closure pada function literal.
- A Tour of Go: more types — latihan resmi function closures.
- Go 1.22 dirilis — Go Blog — pengumuman loop variable per-iterasi.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
closure-demo.go
Unduh berkaspackage main
import "fmt"
// makeCounter mengembalikan closure yang menaikkan hitungannya sendiri.
func makeCounter() func() int {
hitungan := 0
return func() int {
hitungan++
return hitungan
}
}
func main() {
next := makeCounter()
fmt.Println(next())
fmt.Println(next())
fmt.Println(next())
pemanggilLain := makeCounter()
fmt.Println(pemanggilLain())
// Dua closure membagi satu variabel yang sama.
skor := 0
naik := func() {
skor += 10
}
turun := func() {
skor -= 5
}
naik()
naik()
turun()
fmt.Println("skor =", skor)
// Setiap iterasi loop mendapat variabel i sendiri (Go 1.22+).
fungsi := make([]func() int, 0, 3)
for i := 1; i <= 3; i++ {
fungsi = append(fungsi, func() int { return i * 10 })
}
for _, f := range fungsi {
fmt.Println(f())
}
}