Defer ist ein einzigartiger Mechanismus in Go, der es ermöglicht, eine Funktion nach Beendigung der aktuellen Funktion auszuführen, selbst bei Auftreten eines panic. Historisch gesehen ist dies analog zu on-exit Konstruktionen, wird jedoch in Go als Stack von deferred Aufrufen realisiert. Ein wichtiger Punkt ist, dass die Parameter der deferred Funktion sofort beim Anmelden von defer berechnet werden und nicht zum Zeitpunkt ihres tatsächlichen Aufrufs!
Problem — unerwartetes Verhalten: Man könnte erwarten, dass die Parameter beim Auslösen des defer übergeben werden, was jedoch nicht der Fall ist. Dies führt häufig zu Fehlern beim Arbeiten mit veränderbaren oder externen Variablen.
Lösung — Immer im Hinterkopf behalten, dass die Parameter sofort berechnet werden und die Wirkung des deferred Aufrufs später eintritt.
Beispielcode:
func f() { x := 5 defer fmt.Println(x) x = 10 } // Gibt aus: 5, nicht 10
Schlüsselmerkmale:
Was gibt der folgende Code aus? Warum?
func main() { i := 0 defer fmt.Println(i) i = 1 }
Antwort: Gibt 0 aus. Das Argument der Funktion fmt.Println wird sofort bei der Anmeldung des defer gespeichert.
Beeinflusst eine Änderung der Variablen nach der Anmeldung von defer die Übergabe ihres Wertes an die Funktion?
Nein, beeinflusst nicht — die Berechnung erfolgt bei der Anmeldung von defer:
defer fmt.Println(x) // Der Wert von x wird jetzt gespeichert, nicht später
Kann man defer so gestalten, dass der letzte Zustand der Variablen ausgegeben wird?
Ja, mit Hilfe einer anonymen Funktion (Closure):
defer func() { fmt.Println(x) }() // wird den aktuellen Wert von x zum Zeitpunkt des deferred Aufrufs erfassen
Der Code wird so aufgerufen:
var f *os.File // ... defer f.Close()
Aber f wird später zugewiesen, deshalb panic durch den Aufruf eines nil-Pointers!
Vorteile:
Nachteile:
Einwickeln der Bereinigung über eine anonyme deferred Funktion mit Prüfung:
defer func() { if f != nil { f.Close() } }()
Vorteile:
Nachteile: