Alles begann mit Go 1.18: Warum Generics nicht nur eine modische Funktion sind?
\n\nLange Zeit war die Go-Community stolz auf die Einfachheit der Sprache. Das Fehlen generischer Programmierung (Generics) galt als bewusste Entscheidung: „Wir brauchen keine komplexen Vorlagen, wir schreiben verständlichen Code.“ Aber die Realität holte sie ein. Entwickler wurden es leid, Funktionen für int, string und float64 zu kopieren – oder interface{} mit anschließender Typumwandlung zu verwenden und dabei die Sicherheit zur Kompilierzeit zu verlieren.
Die Veröffentlichung von Go 1.18 (2022) war eine Revolution. Generics fügten der Sprache Typparameter (type parameters) und Einschränkungen (constraints) hinzu. Jetzt können Go-Programmierer generische Datenstrukturen und Algorithmen schreiben, ohne auf Leistung und strenge Typisierung zu verzichten.
\n\nIn diesem Artikel auf pythonlib.ru werden wir uns ansehen, wie Generics in Go funktionieren, echte Codebeispiele sehen und verstehen, wann man sie verwenden sollte und wann nicht. Los geht's!
\n\nWas sind Generics in Go in einfachen Worten?
\n\nStellen Sie sich vor, Sie schreiben eine Funktion, die das kleinste Element in einem Slice findet. Ohne Generics müssten Sie drei Versionen erstellen: für int, float64 und string. Oder interface{} mit Typumwandlung verwenden – langsam und unsicher.
Generics ermöglichen es, eine Funktion zu schreiben, die mit jedem Typ arbeitet, der bestimmte Bedingungen erfüllt. Die Syntax ist einfach: Nach dem Funktionsnamen werden in eckigen Klammern die Typparameter angegeben.
\n\nSyntax: Eckige Klammern und das Wort any
\n\npackage main\n\nimport "fmt"\n\n// Min – generische Funktion zur Suche des Minimums\nfunc Min[T any](a, b T) T {\n // Fehler: Operator < ist für any nicht definiert\n // if a < b {\n // return a\n // }\n return b\n}\n\nDer obige Code wird nicht kompiliert, weil any ein leeres Interface ist (Synonym für interface{}). Der Compiler weiß nicht, ob der Typ den Operator < unterstützt. Es muss eine Einschränkung (constraint) hinzugefügt werden.
Type Constraints: Einschränkungen
\n\nIn Go werden Einschränkungen über Interfaces definiert. Man kann die eingebauten aus dem Paket golang.org/x/exp/constraints verwenden oder eigene schreiben.
import (\n "fmt"\n "golang.org/x/exp/constraints"\n)\n\n// Min funktioniert mit jedem geordneten Typ\nfunc Min[T constraints.Ordered](a, b T) T {\n if a < b {\n return a\n }\n return b\n}\n\nfunc main() {\n fmt.Println(Min(10, 20)) // 10\n fmt.Println(Min(3.14, 2.71)) // 2.71\n fmt.Println(Min("apple", "banana")) // "apple"\n}\n\nBeachten Sie: constraints.Ordered umfasst alle Typen, für die die Operatoren <, <=, >, >= definiert sind – das sind int, float, string.
Praktische Beispiele: Vom Einfachen zum Komplexen
\n\nSchauen wir uns an, wie Generics in Go das Schreiben von echtem Code vereinfachen.
\n\n1. Generischer Stack
\n\nOhne Generics müsste man []interface{} speichern und Type Assertions durchführen. Mit Generics – elegant und sicher.
package main\n\nimport "fmt"\n\n// Stack – generische Datenstruktur\ntype Stack[T any] struct {\n items []T\n}\n\nfunc (s *Stack[T]) Push(item T) {\n s.items = append(s.items, item)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n if len(s.items) == 0 {\n var zero T\n return zero, false\n }\n item := s.items[len(s.items)-1]\n s.items = s.items[:len(s.items)-1]\n return item, true\n}\n\nfunc main() {\n intStack := Stack[int]{}\n intStack.Push(1)\n intStack.Push(2)\n val, ok := intStack.Pop()\n fmt.Println(val, ok) // 2 true\n\n strStack := Stack[string]{}\n strStack.Push("hello")\n