Historisch gesehen wurde das Konzept defer in Go eingeführt, um Ressourcen sicher freizugeben und Aktionen unabhängig davon abzuschließen, wie die Funktion endet (normal oder durch panic). Dennoch gibt es einige ärgerliche Fallstricke in der Interaktion von defer mit return und panic, die selbst erfahrenen Entwicklern oft entgehen.
Problem ist, dass die Reihenfolge der Berechnung der Rückgabewerte, die Funktionsweise der benannten Rückgabewerte und die Änderung dieser Werte in defer stark von dem gewohnten Verhalten in vielen Sprachen abweicht. Zudem können Fehler auftreten, wenn in defer versucht wird, bereits berechnete Werte zu ändern, was zu unerwartetem Verhalten führt.
Lösung — immer daran denken: Die Werte, die die Funktion zurückgibt, werden VOR der Ausführung von defer berechnet, aber wenn benannte Ergebnisse verwendet werden, können sie innerhalb von defer bis zur tatsächlichen Rückgabe aus der Funktion geändert werden.
Beispielcode:
func tricky() (res int) { defer func() { res = 42 // Ändert den Rückgabewert! }() return 10 } func main() { fmt.Println(tricky()) // Gibt 42 aus, nicht 10 }
Wichtige Merkmale:
In welcher Reihenfolge werden defer-Funktionen ausgeführt?
Sie werden strikt in umgekehrter Reihenfolge ihrer Deklaration ausgeführt (stack - LIFO).
func f() { defer fmt.Println("1") defer fmt.Println("2") } // Gibt aus: 2, dann 1
Wann werden die Parameter für defer-Funktionen berechnet – beim Deklarieren von defer oder bei deren Ausführung?
Die Parameter für die defer-Funktion werden beim Deklarieren von defer berechnet, nicht bei deren Aufruf.
func f() { i := 1 defer fmt.Println(i) // wird 1 ausgegeben, selbst wenn sich i später ändert i = 2 }
Kann defer das unbenannte Ergebnis der Funktion ändern?
Nein. Nur benannte Rückgabewerte können in defer geändert werden.
func f() int { defer func() { /* kann nichts ändern */ }() return 5 }
Ein junger Entwickler wollte im defer den Rückgabecode protokollieren und änderte versehentlich den benannten Rückgabewert, wodurch das tatsächliche Ergebnis der Funktion "überschrieben" wurde.
Vorteile:
Nachteile:
In einer anderen Situation wurde defer nur zum Freigeben von Ressourcen verwendet, das Protokollieren vorgenommen und der return nicht geändert, während wichtige Werte vor der Rückgabe explizit gesetzt wurden.
Vorteile:
Nachteile: