HeadlinesBriefing HeadlinesBriefing.com

Generische Methoden in Go 1.27

Hacker News •
×

Als Go 1.18 2022 Generics einführte, brachte es generische Typparameter für Funktionen und Structs, aber ließ Methoden außen vor. Mit der Veröffentlichung von Go 1.27 wurde diese langjährige Einschränkung beseitigt. Methoden können nun ihre eigenen Typparameter definieren, ohne sie zum empfangenden Struct hinzuzufügen.

Warum wurde diese Änderung eingeführt? Wenn Sie einen Grafknoten erstellen möchten, der einen generischen Wert speichert, könnten Sie ihn so implementieren:

type Node[T any] struct {

value T

}

Stellen Sie sich vor, Sie fügen eine Map-Methode hinzu, die einen Knoten des Typs T in einen Knoten eines anderen Typs U transformiert. Vor Go 1.27 mussten Sie U direkt zum Node-Struct selbst hinzufügen:

type Node[T any, U any] struct {

value T

}

func (n *Node[T, U]) Map() Node[T, U] {

// ...

}

U direkt zum Struct hinzuzufügen ist schlechtes Design, da U ein Typparameter ist, der spezifisch für Map ist. Selbst wenn andere Methoden U nicht verwenden würden, mussten sie es trotzdem in ihren Receiver-Deklarationen behalten. Die einzige Problemumgehung bestand darin, Map als eine Funktion auf Paketebene anstelle einer Methode zu implementieren, da Funktionen ihre eigenen Typparameter definieren konnten. Methoden konnten dies jedoch nicht, was zu ungewöhnlichen und nicht idiomatischen Code-API-Designs führte. Ab Go 1.27 kann U ausschließlich auf der Map-Methode definiert werden:

func (n *Node[T]) Map[U any]() Node[U] {

// ...

}

Warum hat das so lange gedauert? Die Antwort liegt in der Art und Weise, wie Generics in Go implementiert sind. Der Compiler behandelt Generics hauptsächlich durch Monomorphisierung, was bedeutet, dass er eine Kopie von generischen Structs oder Funktionen für jeden spezifischen Typ erstellt, mit dem sie verwendet werden. Da irgendwann das abstrakte Konzept der Generics in direkten Maschinencode übersetzt werden muss. Allerdings funktioniert das Schnittstellensystem von Go zur Laufzeit. Der spezifische Typ eines Werts, der als Schnittstellenparameter übergeben wird, wird während der Ausführung des Programms aufgelöst. Diese dynamische Dispatches steht im Konflikt mit Generics, die zur Kompilierungszeit aufgelöst werden. Stellen Sie sich vor, wir würden unsere Map-Methode in einer Schnittstelle deklarieren:

type Mapper interface {

Map T any, U any U

}

Um dies funktionieren zu lassen, bräuchte das Laufzeitsystem entweder einen Just-In-Time-Compiler, um Maschinencode für spezifische Typen on-the-fly zu generieren, oder müsste diese Kopien für jeden möglichen Typ im Voraus erstellen, was zu einem massiv aufgeblähten Binärdatei führt. Schließlich entschied sich das Go-Team, generische Methoden von Schnittstellen zu trennen. Go 1.27 erlaubt generische Methoden auf konkreten Typen, aber nicht auf Schnittstellen. Dieser Unterschied ist der Grund, warum das obige Beispiel nicht kompiliert und warum die Diskussionen darum so lange gedauert haben.

Quelle: Hacker News · Zusammengefasst von HeadlinesBriefing