Typ-Parameter in Go 1.18
Typdeklarationen und Funktionsdefinitionen können jetzt mit Typ-Parametern ergänzt werden. Interfaces werden als Constraints eingesetzt, um die generischen Parameter einzuschränken. Dabei wird, ähnlich den normalen Funktionsparametern, eine Liste mit Namen und Constraints angegeben.
Die Typ-Parameter werden in eckigen Klammern nach dem Typen- oder Funktionsnamen angegeben.
func MeineFunktion[T any](slice []T) T { return slice[0] } type MeinStruct[T comparable] struct { Attribut T } type MeinInterface[T constraints.Ordered] interface { Sort([]T) }
Wird ein Type Parameter angegeben, kann er in der Parameterliste (oder den Attributen) verwendet werden. Selbst Interfaces unterstützen die Type Parameter, was eine Kombination von beiden Konzepten zulässt.
Um das Prinzip im Detail zu verdeutlichen, betrachten wir zunächst die einfache Funktion Max, die uns die größere zweier Zahlen zurückgeben soll.
func Max(a, b int32) int32 { if a < b { return a } return b }
In diesem Fall wurde die Funktion mit dem Typ int32 implementiert, der eine 32 Bit lange, vorzeichenbehaftete Ganzzahl darstellt. Wollen wir allerdings das größere zweier Bytes berechnen, so müssen wir das Byte entweder konvertieren oder eine spezifische Version für Bytes schreiben.
Go achtet außerdem sehr streng auf die Datentypen. Anders als in C werden selbst kompatible Datentypen nicht automatisch konvertiert. Wollen wir also alle vordefinierten Datentypen abdecken, müssten wir je eine Funktion für int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64 und string schreiben, von den Typen abgesehen, die daraus abgeleitet sind.
Mit Generics lässt sich die Funktion für alle Typen schreiben, für die die Größenvergleiche (also <, <=, > oder >=) definiert sind. Wir benutzen dafür den Typ Ordered aus dem golang.org/x/exp/constraints-Paket. In der Beta war constraints noch ein Teil der Standardbibliothek, findet sich jetzt aber nur noch unter den experimentellen Paketen in golang.org/x. Der Go-Compiler prüft automatisch, ob die verwendeten Operatoren auch wirklich bei allen Typen zur Verfügung stehen, die der Constraint zulässt. Hätten wir any als Constraint verwendet (also keine Einschränkungen), hätte der Compiler einen Fehler ausgegeben.
func Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b }
Fertig. Alle vierzehn Funktionen in einer. Beim Aufruf der Funktion muss jetzt allerdings der konkrete Typ mit übergeben werden.
m := Max[byte](100, 200)
Go ermöglicht es uns die Angabe auszulassen, wenn alle Datenypen durch die Parameter bereits feststehen. Hier kommt uns die strenge Typisierung zugute.
var a, b byte = 100, 200 m := Max(a, b)
Durch die Constraints ist die Auswahl der Typen eingeschränkt. Der Versuch, das Maximum zweier Slices zu berechnen, würde vom Compiler mit einem Fehler quittiert.
Einschränkungen
Um genauer zu verstehen, wie Go die Constraints anwendet, lohnt sich ein Blick ins Constraints-Paket. Signed ist wie folgt definiert.
type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 }
Zunächst sieht der Code einer normalen Interface-Deklaration sehr ähnlich. Statt der Methoden werden aber Typen aufgezählt. Durch die Pipes | werden mehrere alternative Typen definiert. Der Typ-Parameter dürfte in diesem Fall also ein int, int8, int16, int32 oder int64 sein. Die Tilde bedeutet, dass nicht nur der konkrete Typ, sondern auch alle abgeleiteten Datentypen gemeint sind. Neben einem int wäre auch ein type MyInt int gültig.
Wie bei klassischen Interfaces können auch für die Constraints mehrere bestehende Interfaces kombiniert werden. Die große Einschränkung ist allerdings, dass entweder Methoden oder Datentypen, aber keine Mischung aus beiden benutzt werden können.
type Mixed interface { ~string | String() string // nicht erlaubt }
Mixed würde zu einem Fehler führen, das Go-Team hält sich aber eine zukünftige Unterstützung offen. Was allerdings geht, und davon macht das constraints-Paket regen Gebrauch, ist die Kombination mehrerer Typ-Interfaces. So werden die Integer durch die Verkettung von Signed und Unsigned definiert.
type Integer interface { Signed | Unsigned }
Eine Mischung von konkreten Typen und Typ-Interfaces ist ebenfalls kein Problem. Das Ordered-Interface vereint die Integer und Float Typen mit strings, die in Go ebenfalls die Vergleichsoperationen implementieren.
type Ordered interface { Integer | Float | ~string }
Es gibt eine Art von Constraint, der nicht durch die neue Constraints-Syntax abgedeckt wird. Für Typen, die mit == vergleichbar sind, wurde das neue Schlüsselwort comparable eingeführt. Structs, die nur aus vergleichbaren Typen bestehen, implementieren die Gleichheit automatisch, daher ist eine Auflistung der konkreten Typen nicht möglich. Das Wort comparable wird außerdem für Map-Keys benutzt. Nicht zu verwechseln ist comparable übrigens mit dem Vergleich mit nil, also a == nil oder a != nil. Viele Typen unterstützen den Vergleich mit nil, aber nicht mit anderen Variablen gleichen Typs. Für diese Typen gibt es noch keinen Constraint, er könnte aber in einer zukünftigen Version ergänzt werden.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
- ohne Werbung
- mit ausgeschaltetem Javascript
- mit RSS-Volltext-Feed
Go 1.18: Go wird generisch | Go 1.18: Unser eigener Container |
magst du mal ein Beispiel geben?
Das ein Extrem ist nicht wirklich besser als ein anderes ist, ist glaub jedem klar.