In Go wurden anonyme Funktionen (Funktionsliterale, Closures) zur Unterstützung des funktionalen Stils, von Rückrufen und zur prägnanten Kapselung von Algorithmen eingeführt. Sie werden häufig zur Verarbeitung von Sammlungen, asynchronen Aufgaben und zur Übergabe als Parameter verwendet.
Ohne anonyme Funktionen wird der Code überflüssig: Jede Verarbeitung muss in eine separate benannte Funktion ausgelagert werden. Aber es ergeben sich Fragen: Wie funktioniert das "Capturing" von Variablen, wo wird der Speicher gespeichert, welche Besonderheiten gibt es bei der Deklaration und der Übergabe als Argumente? Ist das Capturing von Variablen geschützt, wenn sie von außen geändert werden? Ein häufiger Fehler ist das inkorrekte Capturing von Variablen in einer Schleife.
Anonyme Funktionen werden als Literale deklariert und können Variablen zugewiesen oder sofort verwendet werden. Wenn eine anonyme Funktion auf Variablen aus dem äußeren Bereich zugreift, werden diese "eingefangen" und für die Lebensdauer der Closure gespeichert. Als Funktionsparameter wird eine anonyme Funktion normalerweise mit einem Typ func übergeben, der mit der Signatur übereinstimmt. Die meisten Probleme treten beim Capturing von Schleifenvariablen auf – hier, wenn die Logik in einer Closure verpackt werden muss, sollten Sie unbedingt eine neue Variable innerhalb der Schleife erstellen.
Beispielcode:
func operate(nums []int, op func(int) int) []int { res := make([]int, len(nums)) for i, n := range nums { res[i] = op(n) } return res } func main() { arr := []int{1, 2, 3} out := operate(arr, func(x int) int {return x * x}) fmt.Println(out) // [1 4 9] }
Wichtige Besonderheiten:
Was passiert beim Capturing einer variablen mit veränderlichem Wert in einer Schleife über eine anonyme Funktion?
Alle Closures fangen dieselbe Variable ein, und beim Aufruf der Funktion nach Verlassen der Schleife erhalten Sie denselben Wert.
Beispielcode:
func main() { a := []func(){} for i := 0; i < 3; i++ { a = append(a, func() { fmt.Println(i) }) } for _, f := range a { f() } // 3 3 3 }
Um dies zu vermeiden, erstellen Sie eine neue Variable im Schleifenrumpf:
for i := 0; i < 3; i++ { j := i a = append(a, func() { fmt.Println(j) }) // 0 1 2 }
Können anonyme Funktionen als Werte vom Typ interface{} verwendet werden?
Funktionen sind nur mit interface{} kompatibel, nicht mit anderen Interfaces, und sie können nicht miteinander verglichen werden (außer mit nil). Wenn Sie eine Closure als interface{} übergeben, können Sie sie nur durch Typumwandlung zur func-Signatur aufrufen.
Können anonyme Funktionen rekursiv sein?
Ja, aber nur wenn zuerst eine benannte Variable für die Closure deklariert wird und dann die Funktion ihr zugewiesen wird.
Beispielcode:
var fib func(n int) int fib = func(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(10)) // 55
In einer Schleife über eine Liste von Callbacks bindet der Entwickler einen Handler als Closure mit dem Capturing der Iteratoren-Variable. Alle Callbacks arbeiten mit einem unangemessenen Wert, was zu Bugs führt.
Vorteile:
Nachteile:
Innerhalb der Schleife wird für jede Closure eine neue Variable erstellt, die ein korrektes Capturing des Wertes und das erwartete Verhalten garantiert.
Vorteile:
Nachteile: