Go 1.18: Unser eigener Container

Ausgestattet mit den Typ-Parametern und Constraints können wir uns an ein etwas anspruchsvolleres Unternehmen wagen. Go verfügt von sich aus über keinen Set-Datentypen. Sammlungen von einzigartigen Werten müssen per map[Typ]struct{} impementiert werden. Die Werte werden als Schlüssel verwendet, als Datentyp benutzen wir struct{} (so sparen wir uns wenigstens den Speicher). Die Nutzung wirkt etwas unelegant, also abstrahieren wir es als generischen Datentyp.

Stellenmarkt
  1. IT Support Specialist (m/w/d)
    soft-nrg Development GmbH, Aschheim-Dornach
  2. Softwareentwickler (m/w/d)
    Comprion GmbH, Paderborn
Detailsuche

  1. type Set[T comparable] map[T]struct{}

Allein ist der Typ natürlich nicht viel besser als die direkte Nutzung von Map. Also erweitern wir ihn um eine Methode, um einen Wert hineinzuschreiben.

  1. func (s Set[T]) Put(elem T) {
  2. s[elem] = struct{}{}
  3. }

Golem Karrierewelt
  1. IT-Grundschutz-Praktiker mit Zertifikat: Drei-Tage-Workshop
    04.-06.07.2022, Virtuell
  2. Blender Grundkurs: virtueller Drei-Tage-Workshop
    07.-09.06.2022, Virtuell
Weitere IT-Trainings

Wir können, wie gehabt, Methoden auf unserem generischen Datentyp definieren. Die Besonderheit ist allerdings, dass der Type Receiver (der Teil in den ersten Klammern) unseren generischen Parameter enthalten muss. Den Constraint müssen wir nicht nochmal wiederholen. Anschließend lässt sich der Typ-Parameter wie gewöhnlich verwenden. Eigene Typ-Parameter sind in den Methoden nicht zugelassen. Das Go-Team ist sich der Einschränkung bewusst, wartet jedoch auf Feedback aus der Community.

Mit ein paar weiteren Methoden ist unser eigener kleiner Datentyp komplett. Eine Len-Funktion ermöglicht es uns, die Anzahl der Elemente zu erfahren, Has prüft, ob ein Element im Set ist, Any gibt ein zufälliges Element zurück.

  1. func (s Set[T]) Len() int {
  2. return len(s)
  3. }
  4.  
  5. func (s Set[T]) Has(elem T) bool {
  6. _, ok := s[elem]
  7. return ok
  8. }
  9.  
  10. func (s Set[T]) Any() T {
  11. for k := range s {
  12. return k
  13. }
  14. var t T
  15. return t
  16. }

Bei der Any-Methode laufen wir in ein kleines Problem. Ist das Set leer, müssen wir ein leeres Element zurückgeben. Go erlaubt es uns allerdings nicht, einen generischen Typ zu initialisieren. Während new(T) und make(T) funktionieren, ist die Anwendbarkeit in unserem Fall eingeschränkt. new würde uns einen Pointer auf die Variable zurückgeben. make funktioniert nur mit integrierten Container-Typen. Eine Kurzinitialisierung per T{} wird verhindert. Wir behelfen uns, indem wir uns eine Variable vom Typ T definieren, die wir aber uninitialisiert lassen. So bekommen wir den leeren Wert für T und können diesen zurückgeben.

Zu guter Letzt bauen wir noch eine Map-Funktion, die eine Funktion f auf jedes Element von s anwendet und das Ergebnis in ein neues Set schreibt. Hier spielt das Generics-System seine volle Stärke aus. Die Typ-Parameter beziehen sich auf die Basistypen des Sets und der Funktion. Der Typ des Zielsets wird erst im Rückgabetyp definiert.

  1. func Map[T, U comparable](set Set[T], f func(T) U) Set[U] {
  2. result := make(Set[U], len(set))
  3. for s := range set {
  4. result.Put(f(s))
  5. }
  6. return result
  7. }

Go kann auch hier bei einem normalen Funktionsaufruf alle Typen korrekt ableiten. Hier wandeln wir alle int in oldSet in entsprechende string in newSet um. Dazu wenden wir einfach die entsprechende Funktion aus der Standardbibliothek auf das alte Set an.

  1. newSet := Map(oldSet, strconv.Itoa)

Performance

Die Go-Entwickler haben sich bei der Implementierung von Generics für Stencils entschieden. Statt für jeden Typ eine eigene Maschinencode-Version der Funktion zu generieren, werden die Typen nach Speichergröße gruppiert. Für jede der Gruppen wird eine separate Funktion generiert und für alle Typen dieser Gruppe benutzt. Findet ein Zugriff auf Attribute der Variablen statt, generiert der Compiler automatisch entsprechende Funktionen für die spezifischen Typen. Dieser Ansatz hat einen großen Vorteil und einen kleinen Nachteil. Der Vorteil ist, dass nicht für jede Kombination von Typen eine Variante der Funktion gebaut werden muss. Das wirkt sich positiv auf die Kompilierzeit und die Größe der Binärdateien aus. Der Nachteil ist, dass wir nicht die volle Geschwindigkeit einer nativen Implementierung bekommen.

Go - Das Praxisbuch: Einstieg in Go und das Go-Ökosystem

Erste Benchmarks ordnen die Performance zwischen einer nativen Implementierung und der Nutzung von Reflection ein. Wie üblich können wir in den nächsten Versionen Optimierungen erwarten.

Fazit

Go 1.18 bringt trotz der großen Sprachänderung keine Anpassungen in der Standardbibliothek mit. Das neue Constraints-Paket wurde mit dem Release Candidate wieder entfernt. Nur die Umbenennung von interface{} in any deutet auf die Generics hin. Die Go-Macher wollten sich zunächst auf die Sprachänderungen konzentrieren, da rückwärtskompatible Änderungen in der Standardbibliothek nicht trivial umzusetzen sind.

Die stabile Version von Go 1.18 war schon für Februar 2022 angekündigt, diesen Termin konnte das Go-Team allerdings nicht halten. Die Release-Notes finden sich auf https://golang.org/doc/go1.18.

Tim Scheuermann ist Software-Entwickler aus Berlin und programmiert seit 2012 in Go. Seine Begeisterung für Computer wurde schon in der Grundschule geweckt und hat ihn durch das Informatikstudium begleitet. Er ist Mitorganisator des Berliner Go Meetups.

Bitte aktivieren Sie Javascript.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
  • ohne Werbung
  • mit ausgeschaltetem Javascript
  • mit RSS-Volltext-Feed
 Typ-Parameter in Go 1.18
  1.  
  2. 1
  3. 2
  4. 3


Aktuell auf der Startseite von Golem.de
Ukrainekrieg
Erster Einsatz einer US-Kamikazedrohne dokumentiert

Eine Switchblade-Drohne hat offenbar einen russischen Panzer getroffen. Dessen Besatzung soll sich auf dem Turm mit Alkohol vergnügt haben.

Ukrainekrieg: Erster Einsatz einer US-Kamikazedrohne dokumentiert
Artikel
  1. Deutsche Bahn: 9-Euro-Ticket gilt nicht in allen Nahverkehrszügen
    Deutsche Bahn
    9-Euro-Ticket gilt nicht in allen Nahverkehrszügen

    So einfach ist es dann noch nicht: Das 9-Euro-Ticket gilt nicht in allen Zügen, die mit einem Nahverkehrsticket genutzt werden können.

  2. Verifone: Bundesweite Störung von Girokarten-Terminals
    Verifone
    Bundesweite Störung von Girokarten-Terminals

    In vielen Geschäften lässt sich derzeit nur bar bezahlen. Ursache ist wohl ein Softwarefehler in Kartenzahlungsterminals für Giro- und Kreditkarten.

  3. Recht auf Vergessenwerden: Verurteilter Raubmörder gewinnt Klage gegen Google
    Recht auf Vergessenwerden
    Verurteilter Raubmörder gewinnt Klage gegen Google

    Das Recht auf Vergessenwerden gilt auch für Raubmörder. Google muss einen Link auf einen Online-Artikel entfernen.

Du willst dich mit Golem.de beruflich verändern oder weiterbilden?
Zum Stellenmarkt
Zur Akademie
Zum Coaching
  • Schnäppchen, Rabatte und Top-Angebote
    Die besten Deals des Tages
    Daily Deals • Days of Play: (u. a. PS5-Controller (alle Farben) günstig wie nie: 49,99€, PS5-Headset Sony Pulse 3D günstig wie nie: 79,99€) • Viewsonic Gaming-Monitore günstiger • Mindstar (u. a. MSI RTX 3090 24GB 1.599€) • Xbox Series X bestellbar • Samsung SSD 1TB 79€ [Werbung]
    •  /