defer wurde in Go entwickelt, um die Verwaltung von Ressourcen (z. B. Dateien, Mutex, Verbindungen) zu erleichtern – in jedem Fall, in dem sichergestellt werden muss, dass Operationen am Ende der Funktion ausgeführt werden. Historisch gesehen gab es ähnliche Konstrukte in anderen Sprachen (finally in Java, try-with-resources), aber Go implementiert ein klareres und verständlicheres Muster.
Problem: Man muss sich immer sicher sein, dass Ressourcen freigegeben werden, selbst wenn ein Fehler auftritt oder ein Panic passiert. Doppelte Aufrufe zur Schließung einer Ressource oder Leckagen sind häufige Probleme im klassischen Programmierstil.
Lösung: Alles, was in einer Funktion oder Methode mit defer deklariert wird, wird auf den Aufrufstapel gelegt und in umgekehrter Reihenfolge vor dem Verlassen der Funktion ausgeführt. Dies stellt sicher, dass Ressourcen auch bei Ausnahmen (Panic) oder vorzeitigen Returns freigegeben werden.
Beispielcode:
func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // die Datei wird am Ende geschlossen // Arbeit mit der Datei return nil }
Wichtige Merkmale:
Werden die defer’s ausgeführt, wenn innerhalb der Funktion ein Panic auftritt?
Ja! Alle defer-Funktionen werden auch im Falle eines Panic aufgerufen, dies ist der Hauptmechanismus der "Finalisierung".
Wann werden die Argumente der Funktion, die in defer übergeben werden, ausgewertet?
Zum Zeitpunkt der Deklaration von defer, nicht wenn sie tatsächlich ausgeführt wird. Daher ist es wichtig zu beachten, wenn Variablen verwendet werden, die später geändert werden:
a := 1 defer fmt.Println(a) a = 2 // gibt 1 aus, nicht 2
Wie funktioniert defer innerhalb einer Schleife? Führt dies zu einem Speicherleck?
Wenn in jeder Iteration der Schleife ein defer verwendet wird, werden alle defer’s erst nach Abschluss der gesamten Funktion ausgeführt und nicht nach jeder Iteration – der gesamte Stack von defer-Funktionen wird angehäuft, was zu übermäßigem Speicherverbrauch führen kann.
for i := 0; i < 3; i++ { defer fmt.Println(i) }
Tausend Dateien werden in einer Schleife geöffnet, und für jede wird defer verwendet. Alle Dateien werden erst am Ende der gesamten Funktion geschlossen und die Ressourcen bleiben gebunden, was zu einem „Leck“ – einer Überschreitung des Limits von geöffneten Dateien – führt.
Vorteile:
Nachteile:
In der Schleife werden lokale Funktionen verwendet, in denen defer nur für den Bereich dieser Datei und nicht für den gesamten Handler angewendet wird:
for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // Arbeit mit f }() }
Vorteile:
Nachteile: