Go Generics: Alles, was Sie wissen müssen
Daniel Hayes
Full-Stack Engineer · Leapcell

Was ist Generics
Generische Programmierung ist ein Stil oder Paradigma von Programmiersprachen. Generics ermöglichen es Programmierern, Code in stark typisierten Programmiersprachen zu schreiben, wobei einige Typen verwendet werden, die später spezifiziert werden, und diese Typen werden als Parameter während der Instanziierung bereitgestellt.
Generics ermöglichen es Ihnen, Code zu schreiben, der auf mehrere Typen angewendet werden kann, ohne die gleiche Logik für jeden Typ zu wiederholen. Dies verbessert die Wiederverwendbarkeit, Flexibilität und Typsicherheit des Codes.
In Go werden Generics durch Typparameter implementiert. Ein Typparameter ist eine spezielle Art von Parameter, der als Platzhalter verwendet wird, der ein beliebiger Typ sein kann. Sie werden in den Definitionen von Funktionen, Methoden und Typen verwendet und durch spezifische Typen während konkreter Aufrufe ersetzt.
Vor Generics
Betrachten Sie diese Anforderung: Implementieren Sie eine Funktion, die zwei int
-Parameter entgegennimmt und den kleineren der beiden zurückgibt. Dies ist eine sehr einfache Anforderung, und wir können leicht den folgenden Code schreiben:
func Min(a, b int) int { if a < b { return a } return b }
Dies sieht großartig aus, aber die Funktion hat eine Einschränkung: Die Parameter können nur vom Typ int
sein. Wenn sich die Anforderung erweitert und wir den Vergleich von zwei float64
-Werten unterstützen und den kleineren zurückgeben müssen, können wir Folgendes schreiben:
func Min(a, b int) int { if a < b { return a } return b } func MinFloat64(a, b float64) float64 { if a < b { return a } return b }
Sie werden feststellen, dass wir, sobald sich die Anforderung erweitert, auch Änderungen vornehmen und ständig sich wiederholende Arbeiten ausführen müssen. Generics sind genau das, was dieses Problem löst.
import "golang.org/x/exp/constraints" func Min[T constraints.Ordered](x, y T) T { if x < y { return x } return y }
Grundlegende Syntax von Generics
// Funktionsdefinition func F[T any](p T){...} // Typdefinition type M[T any] []T // Constraint repräsentiert spezifische Typeinschränkungen, wie any, comparable func F[T Constraint](p T){..} // Das Symbol „~“ wird verwendet, um zugrunde liegende Typeinschränkungen darzustellen type E interface { ~string } // Geben Sie mehrere Typen an type UnionElem interface { int | int8 | int32 | int64 }
Das Symbol
In Go Generics wird das ~
-Symbol verwendet, um eine zugrunde liegende Typeinschränkung darzustellen.
Zum Beispiel bedeutet ~int
, dass jeder Typ akzeptiert wird, dessen zugrunde liegender Typ int
ist, einschließlich benutzerdefinierter Typen. Wenn es einen benutzerdefinierten Typ MyInt
gibt, dessen zugrunde liegender Typ int
ist, kann diese Einschränkung den Typ MyInt
akzeptieren.
type MyInt int type Ints[T int | int32] []T func main() { a := Ints[int]{1, 2} // Korrekt b := Ints[MyInt]{1, 2} // Kompilierungsfehler println(a) println(b) }
MyInt does not satisfy int | int32 (possibly missing ~ for int in int | int32)compilerInvalidTypeArg
Ändern Sie es einfach wie folgt:
type Ints[T ~int | ~int32] []T
Typeinschränkungen
any
: Akzeptiert jeden Typcomparable
: Unterstützt==
und!=
Operationenordered
: Unterstützt Vergleichsoperatoren wie>
,<
Für andere Typen siehe: https://pkg.go.dev/golang.org/x/exp/constraints
Wann man Generics verwendet
Wann man Generics verwendet
- Für Operationen an sprachdefinierten Containertypen: Beim Schreiben von Funktionen, die auf spezielle, von der Sprache definierte Containertypen wie Slices, Maps und Channels angewendet werden, und der Funktionscode keine spezifischen Annahmen über den Elementtyp trifft, kann die Verwendung von Typparametern nützlich sein. Zum Beispiel eine Funktion, die einen Slice aller Schlüssel von einem beliebigen Kartentyp zurückgibt.
- Allgemeine Datenstrukturen: Für allgemeine Datenstrukturen wie verkettete Listen oder Binärbäume kann die Verwendung von Typparametern generischere Datenstrukturen erzeugen, Daten effizienter speichern, Type Assertions vermeiden und vollständige Typprüfungen zur Build-Zeit durchführen.
- Implementieren generischer Methoden: Wenn verschiedene Typen einige gemeinsame Methoden implementieren müssen und diese Implementierungen genau gleich aussehen, kann die Verwendung von Typparametern nützlich sein. Zum Beispiel ein generischer Typ, der
sort.Interface
für jeden Slice-Typ implementiert. - Bevorzuge Funktionen gegenüber Methoden: Wenn Sie so etwas wie eine Vergleichsfunktion benötigen, bevorzugen Sie die Verwendung von Funktionen anstelle von Methoden. Für allgemeine Datentypen ist es vorzuziehen, Funktionen zu verwenden, anstatt Einschränkungen zu schreiben, die Methoden erfordern.
// SliceFn implements sort.Interface for a slice of T. type SliceFn[T any] struct { s []T less func(T, T) bool } func (s SliceFn[T]) Len() int { return len(s.s) } func (s SliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] } func (s SliceFn[T]) Less(i, j int) bool { return s.less(s.s[i], s.s[j]) }
// SortFn sorts s in place using a comparison function. func SortFn[T any](s []T, less func(T, T) bool) { sort.Sort(SliceFn[T]{s, less}) }
Wann man Generics nicht verwenden sollte
- Ersetze keine Interface-Typen: Wenn Sie nur Methoden für einen Wert eines bestimmten Typs aufrufen müssen, sollten Sie Interface-Typen anstelle von Typparametern verwenden. Sie sollten beispielsweise keine Funktion, die Interface-Typen verwendet, in eine Funktion ändern, die Typparameter verwendet.
- Verwenden Sie keine Typparameter, wenn sich die Methodenimplementierungen unterscheiden: Wenn sich die Methodenimplementierung für jeden Typ unterscheidet, sollten Sie Interface-Typen verwenden und verschiedene Methodenimplementierungen schreiben, anstatt Typparameter zu verwenden.
- Verwenden Sie Reflection angemessen: Wenn einige Operationen Typen unterstützen müssen, die nicht einmal Methoden haben, und die Operationen für jeden Typ unterschiedlich sind, verwenden Sie Reflection. Zum Beispiel verwendet das Paket
encoding/json
Reflection.
Einfache Richtlinie
Wenn Sie feststellen, dass Sie genau den gleichen Code mehrmals schreiben, wobei der einzige Unterschied die verwendeten Typen sind, können Sie die Verwendung von Typparametern in Betracht ziehen. Mit anderen Worten, Sie sollten die Verwendung von Typparametern vermeiden, bis Sie feststellen, dass Sie im Begriff sind, genau den gleichen Code mehrmals zu schreiben.
Trivia
Warum werden eckige Klammern []
anstelle der spitzen Klammern < >
verwendet, die in anderen Sprachen üblich sind?
https://github.com/golang/proposal/blob/master/design/15292/2013-12-type-params.md
Verwenden Sie spitze Klammern, wie in Vector. Dies hat den Vorteil, dass es C++- und Java-Programmierern vertraut ist. Leider bedeutet dies, dass f(true) entweder als Aufruf der Funktion f oder als Vergleich von f<T (ein Ausdruck, der testet, ob f kleiner als T ist) mit (true) interpretiert werden kann. Obwohl es möglich ist, komplexe Auflösungsregeln zu erstellen, vermeidet die Go-Syntax diese Art von Mehrdeutigkeit aus gutem Grund.
Wir sind Leapcell, Ihre erste Wahl für das Hosting von Go-Projekten.
Leapcell ist die Next-Gen Serverless Plattform für Webhosting, Async Tasks und Redis:
Multi-Language Support
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt viele Projekte kostenlos bereit
- zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 $ unterstützen 6,94 Mio. Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeit-Metriken und -Protokollierung für umsetzbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand – konzentrieren Sie sich einfach auf das Bauen.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ