Introduction: Why Doesn't Go Say "enum"?
\n\nIf you came to Go from Python, Java, or C#, you've probably experienced a mild culture shock: Go does not have the enum keyword. No built-in enumerations with automatic numbering, no .name() or .valueOf() methods. Just you, the compiler, and… constants.
The question "What is an idiomatic way of representing enums in Go?" is one of the most popular on Stack Overflow in the Golang section (thousands of views). And this is no accident. Every beginner eventually hits a wall: "How do I make an enum the Go way?"
\n\nIn this article, we won't just give the answer. We'll look under the hood: how Go handles constants, what iota is, and why the compiler doesn't consider two different int values as different types. And at the end, you'll learn how to make an enum safe, convenient, and elegant.
The Problem: Why Are Simple Constants Bad?
\n\nThe first thing that comes to mind is to declare several constants:
\n\nconst (\n StatusActive = 1\n StatusInactive = 2\n StatusPending = 3\n)\n\nIt seems to work. But there's a nuance: any function that takes an int will swallow the value 999 or -42. The compiler won't say a word. This isn't an enumeration — it's just numbers with names.
A real enum should:
\n- \n
- Restrict the set of allowed values; \n
- Be type-safe (you can't pass an
intwhere a status is expected); \n - Be readable and easily extensible. \n
Go solves this problem idiomatically: through custom types and iota.
The Idiomatic Way: Type + iota
\n\nHere's what the canonical answer from that very Stack Overflow looks like:
\n\ntype Status int\n\nconst (\n StatusActive Status = iota // 0\n StatusInactive // 1\n StatusPending // 2\n StatusDeleted // 3\n)\n\nWhat happened here?
\n- \n
type Status int— we created a new named type. Yes, under the hood it's anint, but for the compiler,Statusandintare different things. \n iotais a sequential integer generator that only works inside aconstblock. Each new line increments the value by 1. \n
Now a function that takes a Status will not compile if you try to pass it a plain int:
func Process(s Status) {\n // ...\n}\n\nfunc main() {\n Process(StatusActive) // OK\n Process(1) // Compilation error! Cannot use 1 (type int) as type Status\n}\n\nThis is type safety — the first and main reason to use custom types for enums.
\n\nWhy iota Instead of Manual Numbering?
\n\nImagine you added a new status in the middle of the list. With manual numbering, you would have to rewrite all subsequent values. iota does this automatically. It's not just convenient — it's idiomatic.
Under the Hood: How Go Sees an Enum
\n\nLet's break down what actually happens at compile time.
\n\n1. iota is Not Runtime
\n\niota does not exist during program execution. It is a preprocessor tool (more precisely, part of the package compiler). At compile time, all iota are replaced with specific numbers. In the resulting binary, you will simply see 0, 1, 2.
You can verify this with go tool compile or just by looking at the assembly code:
// Source code\nconst (\n A