Eine generische Funktion
Typparameter stehen in eckigen Klammern vor den normalen Parametern. Jeder hat ein Constraint, das festlegt, welche Typen erlaubt sind.
T und U werden aus den Argumenten abgeleitet, also schreibst du selten Map[int, string](...). Die Inferenz arbeitet mit den Funktionsargumenten; einen Typ, der nur im Ergebnis vorkommt, kann sie nicht ableiten. Bei func Zero[T any]() T musst du Zero[int]() schreiben.
Vor Go 1.18 musste diese Funktion für jeden Typ einzeln geschrieben werden oder []interface{} nehmen und die Typsicherheit verlieren.
Constraints
Ein Constraint ist ein Interface. Es begrenzt, welche Typen verwendet werden können, und sagt dem Compiler im Gegenzug, welche Operationen auf Werten dieses Typs erlaubt sind.
| Constraint | Erlaubt | Du kannst nutzen |
|---|---|---|
any | jeden Typ | Zuweisung, Übergabe, Speichern |
comparable | Typen mit == | ==, !=, Map-Schlüssel |
cmp.Ordered (Go 1.21) | Ganzzahlen, Floats, Strings | <, <=, >, >=, dazu == |
| ein Interface mit Methoden | Typen mit diesen Methoden | Aufruf der Methoden |
eine Typ-Union wie ~int | ~float64 | genau diese Typen | Operatoren, die alle davon unterstützen |
Mit any statt comparable kompiliert v == target nicht: invalid operation: v == target (incomparable types in type set). Erst das Constraint macht den Operator erlaubt.
Diese beiden Helfer gibt es schon: slices.Index und slices.Max. Die Standardpakete slices, maps und cmp decken den meisten generischen Alltagsbedarf ab, also schau dort nach, bevor du deine eigenen schreibst.
Eigene Constraints mit Typ-Unions und ~
Ein Constraint kann Typen mit | auflisten. Erlaubt sind die Operatoren, die jeder aufgelistete Typ unterstützt.
Die Tilde zählt. ~int64 bedeutet „jeder Typ, dessen zugrunde liegender Typ int64 ist“, also wird der benannte Typ Cents akzeptiert. Mit einem schlichten int64 scheitert Sum([]Cents{...}) mit Cents does not satisfy Number (possibly missing ~ for int64 in Number).
Interfaces mit Typ-Unions können nur als Constraints verwendet werden, nie als gewöhnliche Variablentypen. var n Number kompiliert nicht.
Das Paket golang.org/x/exp/constraints bietet fertige Constraints wie Integer, Float und Signed. Es liegt außerhalb der Standardbibliothek, also kann der Runner hier es nicht importieren; cmp.Ordered ist das Standard-Constraint.
Generische Typen
Auch Structs, Slices, Maps und andere Typen können Typparameter haben. Methoden auf einem generischen Typ verwenden die Parameter des Receivers.
Zwei Details aus diesem Beispiel tauchen ständig auf:
- Der Nullwert von T. Mit
var zero Tgibst du in generischem Code „nichts“ zurück. EinT{}odernil, das für jeden Typ funktioniert, gibt es nicht. - Instanziierung. Ein generischer Typ muss vor der Verwendung instanziiert werden:
Stack[string], nichtStack. In seinen eigenen Methoden schreibst du den Receiver alsStack[T].
Go 1.24 hat generische Typ-Aliase eingeführt: type Set[T comparable] = map[T]struct{} kompiliert jetzt.
Grenzen, auf die du stoßen wirst
- Keine Typparameter an Methoden.
func (s *Stack[T]) Map[U any](...)ist nicht erlaubt. Nimm eine Funktion auf oberster Ebene. - Keine Spezialisierung. Du kannst keine eigene Implementierung für
T = stringschreiben. Ein Type Switch aufany(v)in der Funktion ist der Workaround und meist ein Hinweis, dass Generics das falsche Werkzeug sind. - Keine Operator-Constraints per Name. Du kannst nicht „jeder Typ mit einer
+-Methode“ sagen; du listest die Typen in einer Union auf. - Feldzugriff über ein Constraint funktioniert nicht. Selbst wenn jeder Typ in einer Union ein Feld
IDhat, istv.IDnicht erlaubt. Nimm stattdessen eine Methode ins Constraint.
Wann du keine Generics nutzen solltest
Generics passen zu Container-Typen (Stacks, Sets, Caches, Bäume) und zu Algorithmen über Slices und Maps, die für jeden Elementtyp dasselbe tun. Sie passen nicht überall.
- Ein einziger konkreter Typ. Wird die Funktion immer nur mit
[]Useraufgerufen, kostet eine generische Version Lesezeit und bringt sonst nichts. - Verhalten, das sich je nach Typ unterscheidet. Dafür gibt es Interfaces. Eine Funktion, die einen
io.Writernimmt, ist einfacher als eine mit[T io.Writer]und funktioniert genauso. - Interface-Parameter ersetzen.
func Print[T fmt.Stringer](v T)ist nicht besser alsfunc Print(v fmt.Stringer).
Die Richtlinie des Go-Teams selbst: Schreib den Code zuerst für einen bestimmten Typ und greif zu Typparametern, wenn du merkst, dass du denselben Code ein zweites Mal schreibst und sich nur die Typen ändern.
Häufige Fehler
anyverwenden, wocomparableodercmp.Orderednötig ist. Der Compiler lehnt==oder<auf einem Typparameter mitanyab.- Das
~vergessen. Selbst definierte Typen wietype ID interfüllen ein schlichtesint-Constraint nicht. nilfür einen Typparameter zurückgeben. Nicht erlaubt, außer das Constraint beschränkt T auf pointerartige Typen. Gib einen Nullwert zurück.- Einen generischen Helfer schreiben, den es schon gibt. Schau zuerst in
slices,mapsundcmp.
Häufig gestellte Fragen
Hat Go Generics?
Ja, seit Go 1.18 (März 2022). Funktionen und Typen können Typparameter in eckigen Klammern deklarieren: func Map[T, U any](s []T, f func(T) U) []U. Jeder Typparameter hat ein Constraint, ein Interface, das beschreibt, was der Typ unterstützen muss.
Was ist der Unterschied zwischen any und comparable bei Go-Generics?
any erlaubt jeden Typ, aber mit den Werten kannst du fast nichts tun außer sie zuzuweisen, zu übergeben und zu speichern. comparable erlaubt nur Typen, die == und != unterstützen, und das brauchst du für Map-Schlüssel oder Gleichheitsprüfungen. Für < und > nimm cmp.Ordered.
Was bedeutet die Tilde (~) in einem Go-Constraint?
~T bedeutet „jeder Typ, dessen zugrunde liegender Typ T ist“. ~int passt auf int und auch auf type Celsius int. Ohne Tilde passt int in einem Constraint nur auf int selbst, also würden selbst definierte Typen abgelehnt.
Können Go-Methoden Typparameter haben?
Nein. Eine Methode kann die Typparameter ihres Receiver-Typs verwenden (func (s *Stack[T]) Push(v T)), aber keine neuen deklarieren. Schreib stattdessen eine generische Funktion auf oberster Ebene: func Map[T, U any](s *Stack[T], f func(T) U) *Stack[U].