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:

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

bash
go run defer-demo.go

Output yang Dihasilkan:

text
-- 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 selesai

Mari kita telaah baris demi baris program di atas:

  1. Baris 9-14 (func urutanLIFO): tiga defer didaftarkan lebih dulu, baru pesan -- urutanLIFO mulai -- dicetak. Output membuktikan urutan LIFO (last in, first out): defer ke-3 dijalankan pertama, defer ke-1 terakhir.
  2. Baris 16-21 (func argumenDievaluasiSaatDefer): nilai bernilai 10 saat defer didaftarkan, lalu diubah menjadi 99. Output menunjukkan argumen tetap 10: argumen defer dievaluasi saat defer didaftarkan, bukan saat pemanggilannya berjalan.
  3. Baris 23-28 (func loopDanDefer): setiap iterasi mendaftarkan satu defer. Nilai i yang dicetak adalah nilai saat pendaftaran (1, 2, 3), lalu dieksekusi terbalik menjadi 3, 2, 1.
  4. Baris 30-40 (func tutupBerkas): membuat berkas sementara dengan os.CreateTemp, lalu defer berkas.Close() menjadwalkan penutupan tepat setelah berkas berhasil dibuat. Ketika cabang galat melakukan return, penutupan tetap terjamin karena pendaftaran defer terjadi sebelum titik return mana pun.
  5. Baris 42-47 (func main): memanggil keempat fungsi berurutan. Setiap fungsi menyelesaikan seluruh defer miliknya sendiri sebelum kendali kembali ke main — 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:

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

bash
go run defer-closure.go

Output yang Dihasilkan:

text
mendaftarkan defer closure dalam perulangan
closure membaca i = 3
closure membaca i = 2
closure membaca i = 1

Closure 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 #

Berkas Sumber

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

defer-closure.go

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

defer-demo.go

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