Generics in Go: Wie generische Programmierung die Spielregeln ändert

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Alles begann mit Go 1.18: Warum Generics nicht nur eine modische Funktion sind?

\n\n

Lange 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.

\n\n

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\n

In 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\n

Was sind Generics in Go in einfachen Worten?

\n\n

Stellen 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.

\n\n

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\n

Syntax: Eckige Klammern und das Wort any

\n\n
package 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\n

Der 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.

\n\n

Type Constraints: Einschränkungen

\n\n

In Go werden Einschränkungen über Interfaces definiert. Man kann die eingebauten aus dem Paket golang.org/x/exp/constraints verwenden oder eigene schreiben.

\n\n
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\n

Beachten Sie: constraints.Ordered umfasst alle Typen, für die die Operatoren <, <=, >, >= definiert sind – das sind int, float, string.

\n\n

Praktische Beispiele: Vom Einfachen zum Komplexen

\n\n

Schauen wir uns an, wie Generics in Go das Schreiben von echtem Code vereinfachen.

\n\n

1. Generischer Stack

\n\n

Ohne Generics müsste man []interface{} speichern und Type Assertions durchführen. Mit Generics – elegant und sicher.

\n\n
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                

Blogs

Buchempfehlungen