Fungsi yang membuka sumber daya wajib menutupnya kembali: berkas, koneksi jaringan, hingga kunci penguncian. Persoalannya, fungsi bisa berakhir lewat banyak pintu — return lebih awal, cabang galat di tengah jalan, bahkan panic — dan menulis penutupan di setiap pintu itu rawan terlewat satu. Kata kunci defer menyelesaikannya sekali jalan: pemanggilan yang ditandai defer dijadwalkan dan pasti berjalan saat fungsi pemanggilnya selesai, apa pun jalur keluarnya. Pada chapter penutup Section ini kita membedah urutan LIFO, momen evaluasi argumen, perilaku bersama closure dan variabel perulangan, serta pola pembersihan berkas.
Menjadwalkan, Bukan Menjalankan #
Ketika Go menemui defer f(x), pemanggilan f tidak terjadi di tempat. Runtime Go menyimpan pemanggilan tersebut ke sebuah tumpukan, lalu melanjutkan eksekusi statement berikutnya. Pemanggilan tertunda baru dijalankan saat fungsi pemanggil kembali ke pemanggilnya — baik lewat return, lewat akhir badan fungsi, maupun saat panic — dan lazim disebut deferred call.
Program utuh memakai beberapa fungsi kecil agar perilaku defer per fungsi terlihat jelas; pendefinisian fungsi dibahas tuntas pada Section Fungsi
. Berkas defer-demo.go pada bagian bawah halaman memuat program yang sama:
package main
import (
"fmt"
"os"
)
func urutanLIFO() {
defer fmt.Println("defer 1: didaftarkan pertama, dijalankan terakhir")
defer fmt.Println("defer 2")
defer fmt.Println("defer 3: didaftarkan terakhir, dijalankan pertama")
fmt.Println("-- urutanLIFO mulai --")
}
func argumenDievaluasiSaatDefer() {
nilai := 10
defer fmt.Println("argumen defer menangkap nilai:", nilai)
nilai = 99
fmt.Println("nilai setelah diubah:", nilai)
}
func loopDanDefer() {
fmt.Println("mendaftarkan defer dalam perulangan")
for i := 1; i <= 3; i++ {
defer fmt.Println("argumen i =", i)
}
}
func tutupBerkas() {
berkas, err := os.CreateTemp("", "defer-demo-*.txt")
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
defer berkas.Close()
fmt.Println("berkas sementara dibuat dan ditutup otomatis saat fungsi selesai")
}
func main() {
urutanLIFO()
argumenDievaluasiSaatDefer()
loopDanDefer()
tutupBerkas()
}Jalankan program dari direktori chapter dengan perintah berikut:
go run defer-demo.goOutput yang Dihasilkan:
-- urutanLIFO mulai --
defer 3: didaftarkan terakhir, dijalankan pertama
defer 2
defer 1: didaftarkan pertama, dijalankan terakhir
nilai setelah diubah: 99
argumen defer menangkap nilai: 10
mendaftarkan defer dalam perulangan
argumen i = 3
argumen i = 2
argumen i = 1
berkas sementara dibuat dan ditutup otomatis saat fungsi selesaiMari kita telaah baris demi baris program di atas:
- Baris 9-14 (
func urutanLIFO): tigadeferdidaftarkan lebih dulu, baru pesan-- urutanLIFO mulai --dicetak. Output membuktikan urutan LIFO (last in, first out): defer ke-3 dijalankan pertama, defer ke-1 terakhir. - Baris 16-21 (
func argumenDievaluasiSaatDefer):nilaibernilai 10 saatdeferdidaftarkan, lalu diubah menjadi 99. Output menunjukkan argumen tetap 10: argumen defer dievaluasi saatdeferdidaftarkan, bukan saat pemanggilannya berjalan. - Baris 23-28 (
func loopDanDefer): setiap iterasi mendaftarkan satudefer. Nilaiiyang dicetak adalah nilai saat pendaftaran (1, 2, 3), lalu dieksekusi terbalik menjadi 3, 2, 1. - Baris 30-40 (
func tutupBerkas): membuat berkas sementara denganos.CreateTemp, laludefer berkas.Close()menjadwalkan penutupan tepat setelah berkas berhasil dibuat. Ketika cabang galat melakukanreturn, penutupan tetap terjamin karena pendaftarandeferterjadi sebelum titikreturnmana pun. - Baris 42-47 (
func main): memanggil keempat fungsi berurutan. Setiap fungsi menyelesaikan seluruhdefermiliknya sendiri sebelum kendali kembali kemain— urutan antarfungsi tetap rapi.
defer, Closure, dan Variabel Perulangan #
Bentuk defer fmt.Println(i) menangkap nilai i pada saat pendaftaran. Bentuk lain memakai closure: defer func() { ... }() menunda seluruh badan fungsi, sehingga isi variabel dibaca saat closure dieksekusi. Berkas kedua defer-closure.go memperlihatkan bedanya:
package main
import "fmt"
func main() {
fmt.Println("mendaftarkan defer closure dalam perulangan")
for i := 1; i <= 3; i++ {
defer func() {
fmt.Println("closure membaca i =", i)
}()
}
}Jalankan berkas tersebut dari direktori chapter:
go run defer-closure.goOutput yang Dihasilkan:
mendaftarkan defer closure dalam perulangan
closure membaca i = 3
closure membaca i = 2
closure membaca i = 1Closure membaca i ketika badannya dijalankan, dan sejak Go 1.22 variabel perulangan dibuat baru pada setiap iterasi — seperti dijelaskan di chapter Perulangan for
. Tiap closure menangkap salinan i milik iterasinya sendiri, sehingga hasilnya tetap 3, 2, 1. Pada Go 1.21 dan sebelumnya, seluruh closure berbagi satu variabel i yang sama, dan outputnya menjadi 4, 4, 4 — nilai i setelah perulangan selesai.
Pola Pembersihan dan Interaksi dengan panic #
Pola defer paling produktif adalah pembersihan sumber daya: defer berkas.Close() tepat setelah berkas terbuka, defer mu.Unlock() setelah penguncian, defer resp.Body.Close() setelah permintaan HTTP. Penempatan defer segera setelah sumber daya diperoleh memastikan tidak ada jalur keluar yang melupakan penutupan. Saat panic mengguncang program, seluruh defer tetap dijalankan sebelum program berhenti — inilah fondasi mekanisme recover yang kita bahas di chapter Panic dan Recover
.
Dengan defer, rangkaian kendali alur Go lengkap: keputusan lewat if dan switch, pengulangan lewat for beserta range, lompatan terkendali lewat break, continue, dan label, serta jaminan pembersihan lewat defer. Seluruh alat ini akan terus terpakai di program berikutnya — dimulai dari Section Fungsi
tempat kita menyusun kode ke blok yang dapat dipanggil ulang.
Referensi #
- Spesifikasi Go: Defer statements
— definisi resmi semantik
defer. - Blog Go: Defer, Panic, and Recover — artikel klasik dari tim Go.
- Rilis Go 1.22 — perubahan variabel perulangan per-iterasi.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
defer-closure.go
Unduh berkaspackage main
import "fmt"
func main() {
fmt.Println("mendaftarkan defer closure dalam perulangan")
for i := 1; i <= 3; i++ {
defer func() {
fmt.Println("closure membaca i =", i)
}()
}
}
defer-demo.go
Unduh berkaspackage main
import (
"fmt"
"os"
)
func urutanLIFO() {
defer fmt.Println("defer 1: didaftarkan pertama, dijalankan terakhir")
defer fmt.Println("defer 2")
defer fmt.Println("defer 3: didaftarkan terakhir, dijalankan pertama")
fmt.Println("-- urutanLIFO mulai --")
}
func argumenDievaluasiSaatDefer() {
nilai := 10
defer fmt.Println("argumen defer menangkap nilai:", nilai)
nilai = 99
fmt.Println("nilai setelah diubah:", nilai)
}
func loopDanDefer() {
fmt.Println("mendaftarkan defer dalam perulangan")
for i := 1; i <= 3; i++ {
defer fmt.Println("argumen i =", i)
}
}
func tutupBerkas() {
berkas, err := os.CreateTemp("", "defer-demo-*.txt")
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
defer berkas.Close()
fmt.Println("berkas sementara dibuat dan ditutup otomatis saat fungsi selesai")
}
func main() {
urutanLIFO()
argumenDievaluasiSaatDefer()
loopDanDefer()
tutupBerkas()
}