Einleitung: Was sind Generics und warum wurden sie in Go eingeführt?
\nLange Zeit blieb die Sprache Go (Golang) eine der wenigen modernen Sprachen ohne Unterstützung für generische Programmierung (Generics). Entwickler mussten entweder Code für verschiedene Datentypen duplizieren oder Schnittstellen und leere interface{} verwenden, was zu einem Verlust der Typsicherheit und der Notwendigkeit führte, Typumwandlungen zur Laufzeit durchzuführen.
Mit der Veröffentlichung von Go 1.18 (Februar 2022) wurden die lang erwarteten Generics in die Sprache eingeführt. Generics sind ein Mechanismus, der es ermöglicht, Funktionen, Typen und Datenstrukturen zu schreiben, die mit beliebigen Typen arbeiten, während gleichzeitig eine strenge Typisierung zur Kompilierzeit erhalten bleibt.
\nIn diesem Artikel werden wir behandeln:
\n- \n
- Warum Generics in Go nützlich sind; \n
- Wie man generische Funktionen und Typen deklariert; \n
- Was Constraints sind und wie man sie verwendet; \n
- Praktische Anwendungsbeispiele. \n
Warum sind Generics in Go nützlich?
\nVor der Einführung von Generics sah ein typisches Problem so aus: Man musste eine Funktion schreiben, die mit Slices verschiedener Typen arbeitet (z. B. int, string, float64).
Ohne Generics musste man entweder drei separate Funktionen schreiben:
\nfunc SumInts(nums []int) int {\n total := 0\n for _, v := range nums {\n total += v\n }\n return total\n}\n\nfunc SumFloats(nums []float64) float64 {\n total := 0.0\n for _, v := range nums {\n total += v\n }\n return total\n}\n\nOder interface{} mit Typumwandlung verwenden – unsicher und umständlich.
Mit Generics reicht eine einzige Funktion:
\nfunc Sum[T int | float64](nums []T) T {\n var total T\n for _, v := range nums {\n total += v\n }\n return total\n}\n\nDie Vorteile liegen auf der Hand:
\n- \n
- Typsicherheit – Typfehler werden zur Kompilierzeit abgefangen; \n
- Reduzierung von Code-Duplizierung – eine Funktion für viele Typen; \n
- Lesbarkeit – der Code wird prägnanter und verständlicher. \n
Grundlagen der Generics-Syntax in Go
\nDeklaration einer generischen Funktion
\nDie grundlegende Syntax verwendet eckige Klammern [] zur Angabe der Typparameter:
func Funktionsname[Typname constraint](Parameter) Ergebnis {\n // Funktionskörper\n}\n\nBeispiel einer einfachen Funktion, die den übergebenen Wert zurückgibt:
\nfunc Identity[T any](value T) T {\n return value\n}\n\nfunc main() {\n fmt.Println(Identity[int](42)) // 42\n fmt.Println(Identity[string]("Go")) // Go\n}\n\nGo kann den Typ automatisch ableiten, daher ist die explizite Angabe von [int] nicht erforderlich:
fmt.Println(Identity(42)) // Go erkennt selbst, dass T = int\nfmt.Println(Identity("Go")) // T = string\n\n\nGenerische Typen (Strukturen)
\nGenerics funktionieren nicht nur mit Funktionen, sondern auch mit benutzerdefinierten Typen:
\ntype Stack[T any] struct {\n elements []T\n}\n\nfunc (s *Stack[T]) Push(value T) {\n s.elements = append(s.elements, value)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n if len(s.elements) == 0 {\n var zero T\n return zero, false\n }\n value := s.elements[len(s.elements)-1]\n s.elements = s.elements[:len(s.elements)-1]\n return value, true\n}\n\nfunc main() {\n intStack := Stack[int]{}\n intStack.Push(10)\n intStack.Push(20)\n val, ok := intStack.Pop()\n fmt.Println(val, ok) // 20 true\n}\n\n\nConstraints: Einschränkungen für Typen
\nIn den obigen Beispielen haben wir any verwendet – dies ist ein eingebauter Constraint, der "beliebiger Typ" bedeutet. Oft ist es jedoch erforderlich, Einschränkungen aufzuerlegen: z. B. dass der Typ Vergleiche oder arithmetische Operationen unterstützt.