Enums in Go: idiomatischer Weg und Magie unter der Haube

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum sagt Go nicht „enum“?

\n\n

Wenn Sie von Python, Java oder C# zu Go kommen, haben Sie wahrscheinlich einen leichten Kulturschock erlebt: Go hat kein Schlüsselwort enum. Keine eingebauten Aufzählungen mit automatischer Nummerierung, keine Methoden .name() oder .valueOf(). Nur Sie, der Compiler und … Konstanten.

\n\n

Die Frage „What is an idiomatic way of representing enums in Go?“ ist eine der beliebtesten auf Stack Overflow im Bereich Golang (tausende Aufrufe). Und das ist kein Zufall. Jeder Anfänger stößt früher oder später auf die Wand: „Wie erstelle ich ein Enum, das Go-typisch ist?“.

\n\n

In diesem Artikel geben wir nicht nur eine Antwort. Wir schauen unter die Haube: wie Go Konstanten verarbeitet, was iota ist und warum der Compiler zwei verschiedene int-Werte nicht als unterschiedliche Typen betrachtet. Und am Ende erfahren Sie, wie Sie ein Enum sicher, praktisch und elegant gestalten.

\n\n

Das Problem: Warum sind einfache Konstanten schlecht?

\n\n

Das Erste, was einem in den Sinn kommt, ist, mehrere Konstanten zu deklarieren:

\n\n
const (\n    StatusActive   = 1\n    StatusInactive = 2\n    StatusPending  = 3\n)
\n\n

Das scheint zu funktionieren. Aber es gibt einen Haken: Jede Funktion, die ein int akzeptiert, schluckt den Wert 999 oder -42. Der Compiler sagt kein Wort. Das ist keine Aufzählung – das sind nur Zahlen mit Namen.

\n\n

Ein echtes Enum sollte:

\n
    \n
  • Die Menge der zulässigen Werte einschränken;
  • \n
  • Typsicher sein (man kann kein int dort übergeben, wo ein Status erwartet wird);
  • \n
  • Lesbar und leicht erweiterbar sein.
  • \n
\n\n

Go löst diese Aufgabe idiomatisch: durch benutzerdefinierte Typen und iota.

\n\n

Der idiomatische Weg: Typ + iota

\n\n

So sieht die kanonische Antwort aus dem besagten Stack Overflow aus:

\n\n
type Status int\n\nconst (\n    StatusActive   Status = iota // 0\n    StatusInactive               // 1\n    StatusPending                // 2\n    StatusDeleted                // 3\n)
\n\n

Was ist hier passiert?

\n
    \n
  • type Status int – wir haben einen neuen benannten Typ erstellt. Ja, unter der Haube ist es ein int, aber für den Compiler sind Status und int verschiedene Dinge.
  • \n
  • iota ist ein Generator für aufeinanderfolgende ganze Zahlen, der nur innerhalb eines const-Blocks funktioniert. Jede neue Zeile erhöht den Wert um 1.
  • \n
\n\n

Jetzt wird eine Funktion, die ein Status akzeptiert, nicht kompiliert, wenn Sie versuchen, ihr ein normales int zu übergeben:

\n\n
func Process(s Status) {\n    // ...\n}\n\nfunc main() {\n    Process(StatusActive) // OK\n    Process(1)            // Kompilierungsfehler! Cannot use 1 (type int) as type Status\n}
\n\n

Das ist Typsicherheit – der erste und wichtigste Grund, benutzerdefinierte Typen für Enums zu verwenden.

\n\n

Warum iota und nicht manuelle Nummerierung?

\n\n

Stellen Sie sich vor, Sie fügen einen neuen Status in der Mitte der Liste hinzu. Bei manueller Nummerierung müssten Sie alle nachfolgenden Werte umschreiben. iota macht das automatisch. Das ist nicht nur praktisch – es ist idiomatisch.

\n\n

Unter der Haube: Wie Go ein Enum sieht

\n\n

Lassen Sie uns verstehen, was während der Kompilierung tatsächlich passiert.

\n\n

1. iota ist kein Runtime-Konstrukt

\n\n

iota existiert während der Programmausführung nicht. Es ist ein Werkzeug des Präprozessors (genauer gesagt, Teil des Paketcompilers). Während der Kompilierung werden alle iota durch konkrete Zahlen ersetzt. Im resultierenden Binärprogramm sehen Sie einfach 0, 1, 2.

\n\n

Sie können dies mit go tool compile überprüfen oder einfach den Assembler-Code ansehen:

\n\n
// Quellcode\nconst (\n    A                

Blogs

Buchempfehlungen