defer został wprowadzony w Go, aby uprościć zarządzanie zasobami (np. plikami, mutexami, połączeniami) — w każdym przypadku, gdy należy zagwarantować wykonanie operacji na końcu działania funkcji. Historycznie podobne konstrukcje istniały w innych językach (finally w Javie, try-with-resources), ale Go realizuje bardziej jasny i zrozumiały wzór.
Problem: Należy zawsze upewnić się, że zasoby są zwalniane, nawet jeśli wystąpi błąd lub nastąpi panic. Podwójne wywołanie zamknięcia zasobu lub wycieki to częsty problem w klasycznym stylu programowania.
Rozwiązanie: Wszystko, co jest zadeklarowane przez defer w funkcji lub metodzie, jest umieszczane w stosie wywołań i zostanie wykonane w odwrotnej kolejności przed wyjściem z funkcji. Gwarantuje to zwolnienie zasobów nawet w przypadku wyjątków (panic) lub przedwczesnego zwrotu.
Przykład kodu:
func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // zamknięcie pliku nastąpi na końcu // praca z plikiem return nil }
Kluczowe cechy:
Czy defer'y zostaną wykonane, jeśli wewnątrz funkcji wystąpi panic?
Tak! Wszystkie funkcje defer zostaną wywołane nawet w przypadku panic, to jest podstawowy mechanizm "finalizacji".
Kiedy obliczane są argumenty funkcji przekazywane do defer?
W momencie deklaracji defer, a nie kiedy jest rzeczywiście wykonywane. Dlatego jeśli używa się zmiennych, które później są zmieniane, należy to wziąć pod uwagę:
a := 1 defer fmt.Println(a) a = 2 // wyświetli 1, a nie 2
Jak działa defer wewnątrz pętli? Czy nie prowadzi to do wycieków pamięci?
Jeśli w każdej iteracji pętli używa się defer, to wszystkie defery zostaną wykonane dopiero po zakończeniu całej funkcji, a nie po każdej iteracji — cały stos funkcji defer będzie się kumulować, co może prowadzić do nadmiernego zużycia pamięci.
for i := 0; i < 3; i++ { defer fmt.Println(i) }
Otwieranie tysiąca plików w pętli, i dla każdego używa się defer. Wszystkie pliki zostaną zamknięte dopiero na końcu całej funkcji, a zasoby będą utrzymywane, co prowadzi do "wycieku" — przekroczenia limitu otwartych plików.
Zalety:
Wady:
W pętli używane są lokalne funkcje, gdzie defer stosuje się tylko w zakresie tego pliku, a nie dla całego handlera:
for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // praca z f }() }
Zalety:
Wady: