Defer to unikalny mechanizm w Go, który pozwala na wykonanie funkcji po zakończeniu bieżącej funkcji, nawet w przypadku wystąpienia panic. Historycznie jest to analogiczne do konstrukcji on-exit, ale w Go zaimplementowane jako stos wywołań deferred. Ważny niuans — parametry funkcji deferred obliczane są natychmiast w momencie deklaracji defer, a nie w chwili jej rzeczywistego wywołania!
Problem — nieoczywiste zachowanie: można się spodziewać, że parametry są przekazywane w momencie uruchomienia defer, ale tak nie jest. Często prowadzi to do błędów przy pracy z zmiennymi lub zewnętrznymi zmiennymi.
Rozwiązanie — zawsze mieć na uwadze, że parametry są obliczane natychmiast, a efekt wywołania deferred następuje później.
Przykład kodu:
func f() { x := 5 defer fmt.Println(x) x = 10 } // Wyświetli: 5, a nie 10
Kluczowe cechy:
Co wyświetli poniższy kod? Dlaczego?
func main() { i := 0 defer fmt.Println(i) i = 1 }
Odpowiedź: wyświetli 0. Argument funkcji fmt.Println jest przechowywany natychmiast po deklaracji defer.
Czy zmiana zmiennej po deklaracji defer ma wpływ na przekazanie jej wartości do funkcji?
Nie, nie ma wpływu — obliczenie następuje w momencie deklaracji defer:
defer fmt.Println(x) // Wartość x jest przechowywana teraz, nie później
Czy można zrobić defer, aby ostatecznie wyświetlić ostatni stan zmiennej?
Tak, za pomocą funkcji anonimowej (closure):
defer func() { fmt.Println(x) }() // uchwyci aktualną wartość x w momencie wywołania deferred
Kod jest wywoływany tak:
var f *os.File // ... defer f.Close()
Ale f jest przypisywane później, więc panic z powodu wywołania nil pointer!
Zalety:
Wady:
Owijanie działania czyszczącego przez anonimową funkcję deferred z kontrolą:
defer func() { if f != nil { f.Close() } }()
Zalety:
Wady: