Einleitung: Was sind Generics und warum wurden sie in Go eingeführt?
Lange Zeit kam die Go-Community (Golang) ohne Generics (generische Programmierung) aus. Entwickler verwendeten Interfaces, leere interface{} und Codegenerierung, um die Einschränkungen der strengen Typisierung zu umgehen. In Version Go 1.18 (Februar 2022) wurden schließlich Generics zur Sprache hinzugefügt – ein leistungsstarkes Werkzeug, mit dem Sie Funktionen und Strukturen schreiben können, die mit verschiedenen Datentypen arbeiten, ohne die Typsicherheit zu verlieren.
In diesem Artikel werden wir uns ansehen, warum Generics in Go benötigt werden, wie sie funktionieren, und lernen, sie in der Praxis anzuwenden. Das Material richtet sich an Anfänger, die bereits mit den Grundlagen von Go vertraut sind: Variablen, Funktionen und Slices.
Warum werden Generics in Go benötigt?
Stellen Sie sich vor, Sie müssen eine Funktion schreiben, die den Minimalwert in einem Zahlen-Slice findet. Ohne Generics müssten Sie separate Funktionen für int, float64 und andere Typen schreiben:
// Ohne Generics – drei identische Funktionenfunc MinInt(a, b int) int { if a < b { return a } return b}
func MinFloat64(a, b float64) float64 { if a < b { return a } return b}Dies führt zu Code-Duplizierung. Früher wurde dieses Problem mit interface{} und Typkonvertierung gelöst, aber dieser Ansatz verlor die Sicherheit zur Kompilierzeit:
func Min(a, b interface{}) interface{} { // Vergleich und Konvertierung – leicht Fehler zu machen return a}Generics lösen drei zentrale Aufgaben:
- Typsicherheit – Typfehler werden zur Kompilierzeit abgefangen.
- Vermeidung von Duplizierung – eine Funktion arbeitet mit beliebigen Typen.
- Lesbarkeit des Codes – explizite Angabe, welche Typen unterstützt werden.
Grundlagen der Syntax: Type Parameters und Constraints
Generics in Go basieren auf zwei Konzepten: Typparametern (Type Parameters) und Einschränkungen (Constraints).
Typparameter
Typparameter werden in eckigen Klammern nach dem Funktions- oder Typnamen angegeben. Üblicherweise wird der Buchstabe T (von Type) verwendet, aber es können beliebige Namen verwendet werden:
// Funktion mit einem Typparameter Tfunc Print[T any](value T) { fmt.Println(value)}
// VerwendungPrint[int](42) // Typ explizit angebenPrint("Hello") // Compiler leitet den Typ automatisch abDas Schlüsselwort any ist ein Alias für das leere Interface interface{}. Es bedeutet, dass die Funktion jeden beliebigen Typ akzeptiert.
Einschränkungen (Constraints)
Einschränkungen legen fest, welche Typen mit einem Generic verwendet werden können. Zum Beispiel muss ein Typ die Operatoren < und > unterstützen, um Zahlen vergleichen zu können. Integrierte Einschränkungen für numerische Typen befinden sich im Paket golang.org/x/exp/constraints:
import "golang.org/x/exp/constraints"
// Einschränkung – nur ganze Zahlenfunc Sum[T constraints.Integer](numbers []T) T { var total T for _, n := range numbers { total += n } return total}Sie können auch eigene Einschränkungen über Interfaces erstellen:
// Eigene Einschränkung: Der Typ muss eine Methode String() habentype Stringer interface { String() string}
func Describe[T Stringer](value T) string { return "Value: " + value.String()}Praktische Beispiele zur Verwendung von Generics
Betrachten wir einige reale Szenarien, in denen Generics den Code erheblich vereinfachen.
1. Universelle Suchfunktion in einem Slice
// Findet den Index eines Elements in einem Slice. -1 wenn nicht gefunden.func Index[T comparable](slice []T, target T