Statement defer pada Go mengevaluasi nilai fungsi dan parameter pemanggilannya ketika eksekusi mencapai statement tersebut, meskipun fungsi yang ditunda baru berjalan saat fungsi di sekitarnya melakukan return. Mutasi di antara dua momen itu tidak mengubah secara retroaktif nilai argumen yang sudah disimpan.

Pemisahan antara evaluasi dan invocation ini merupakan bagian dari semantik bahasa, bukan detail optimisasi. Setiap defer yang dieksekusi mencatat sebuah pemanggilan dengan nilai yang ditetapkan pada saat itu. Proses return kemudian menjalankan pemanggilan yang tercatat dalam urutan registrasi terbalik.

Nilai argumen ditetapkan pada titik registrasi

Variabel yang diteruskan langsung sebagai argumen dievaluasi sebelum statement berikutnya dapat mengubahnya:

func emit() {
    n := 4
    defer fmt.Println(n)
    n = 9
}

Pemanggilan yang ditunda mencetak 4. Assignment mengubah n, tetapi parameter pemanggilan sudah dievaluasi dan disimpan.

Batas yang sama berlaku pada expression dengan efek yang terlihat:

func closeLater() {
    defer release(acquire())
    work()
}

acquire() berjalan saat statement defer dieksekusi. Hanya release(...) yang menunggu sampai fungsi melakukan return. Kode yang menganggap seluruh call expression ditunda dapat menyebabkan allocation, locking, logging, atau efek lain terjadi lebih awal daripada yang dimaksud.

Closure memindahkan pembacaan variabel ke waktu invocation

Function literal dapat menghasilkan perilaku berbeda karena deferred call itu sendiri mungkin tidak memiliki parameter yang membawa nilai yang berubah:

func emit() {
    n := 4
    defer func() {
        fmt.Println(n)
    }()
    n = 9
}

Versi ini mencetak 9. Registrasi mengevaluasi nilai fungsi, sedangkan body membaca n kemudian ketika fungsi yang ditunda berjalan.

Perbedaannya terletak antara nilai argumen yang disimpan dan variabel yang direferensikan closure. Ini bukan pengecualian khusus pada defer; kedua bentuk mengevaluasi expression yang berbeda saat registrasi.

Closure berparameter mengembalikan capture nilai ke waktu registrasi:

func emit() {
    n := 4
    defer func(v int) {
        fmt.Println(v)
    }(n)
    n = 9
}

Di sini n merupakan argumen bagi deferred call, sehingga nilainya disimpan sebagai 4.

Method receiver merupakan bagian dari pemanggilan yang disimpan

Evaluasi receiver mengikuti aturan yang sama seperti parameter pemanggilan biasa. Deferred method call mengikat receiver saat defer dieksekusi.

Untuk value receiver, hal ini dapat mempertahankan salinan nilai yang berbeda dari state berikutnya:

type Counter struct {
    n int
}

func (c Counter) report() {
    fmt.Println(c.n)
}

func run() {
    c := Counter{n: 4}
    defer c.report()
    c.n = 9
}

Deferred call melaporkan 4 karena value receiver dievaluasi sebagai bagian dari registrasi pemanggilan.

Pointer receiver menyimpan nilai pointer, bukan snapshot objek yang ditunjuk. Mutasi pada objek yang sama sebelum return tetap terlihat melalui pointer tersebut:

func (c *Counter) report() {
    fmt.Println(c.n)
}

func run() {
    c := &Counter{n: 4}
    defer c.report()
    c.n = 9
}

Bentuk ini melaporkan 9. Receiver yang disimpan tetap menunjuk Counter yang sama.

Reassignment dan mutasi merupakan efek yang berbeda

Menyimpan nilai pointer, slice, map, channel, function, atau interface tidak melakukan deep copy atas data yang dapat dijangkau melalui nilai tersebut. Argumennya sendiri tetap, sedangkan state yang direferensikan masih dapat berubah.

func run() {
    values := []int{4}
    defer fmt.Println(values)
    values[0] = 9
}

Nilai slice yang disimpan memuat data pointer, length, dan capacity. Perubahan backing array mengubah apa yang kemudian diamati formatter yang ditunda.

Reassignment pada variabel slice lokal berbeda:

func run() {
    values := []int{4}
    defer fmt.Println(values)
    values = []int{9}
}

Argumen yang ditunda mempertahankan nilai slice sebelumnya, sehingga assignment variabel berikutnya tidak mengganti argumen yang telah disimpan.

Batas antara nilai dan referent ini sangat relevan untuk cleanup call. Menyimpan nilai handle melindungi deferred call dari reassignment variabel lokal berikutnya, tetapi tidak membekukan mutable state di balik handle tersebut.

Return value ditetapkan sebelum deferred call berjalan

Deferred invocation terjadi setelah result parameter ditetapkan oleh return statement dan sebelum kontrol mencapai caller. Urutan ini memungkinkan closure mengamati atau mengubah named result parameter:

func result() (n int) {
    defer func() {
        n *= 2
    }()
    return 6
}

Return statement menetapkan 6 ke n; deferred closure kemudian mengubahnya menjadi 12.

Argumen langsung tetap mempertahankan semantik waktu registrasi:

func result() (n int) {
    defer fmt.Println(n)
    n = 6
    return
}

Deferred print menerima nilai n dari titik ketika defer dieksekusi, bukan nilai yang ada selama proses return.

Urutan invocation terbalik tidak membalik urutan evaluasi

Beberapa defer berjalan dalam urutan last-in, first-out, tetapi argumennya dievaluasi dalam urutan eksekusi normal saat setiap statement dicapai:

defer record(first())
defer record(second())

first() dieksekusi sebelum second() selama registrasi. Saat return, record untuk nilai kedua yang disimpan berjalan sebelum record untuk nilai pertama.

Urutan evaluasi dan urutan deferred invocation dengan demikian membentuk dua timeline berbeda. Efek samping pada argument expression terjadi pada jalur maju melalui fungsi; body fungsi yang ditunda berjalan pada jalur terbalik selama return.

Pemisahan itu merupakan batas utama defer: registrasi melakukan evaluasi pemanggilan dan menyimpan nilai fungsi beserta parameternya, sedangkan proses return melakukan invocation.