Go has no enum keyword. You build an enum from two pieces: a named type, and a const block of values of that type, numbered with iota.
Sunday is 0, and each following line is one more. The named type Weekday is what makes this an enum rather than a list of numbers: isWeekend says in its signature what it expects, and methods can be added to the type. The output prints 1 5 6 because nothing yet tells Go how to show a Weekday as text. That comes below.
How iota Works
iota is a counter the compiler provides inside a const block. Two rules explain every trick built on it:
iotaequals the index of the current line in the block, starting at 0, and resets to 0 in each newconstblock.- A line with no
= expressionrepeats the previous line's expression and type, evaluated with the newiota.
So Monday above is shorthand for Monday Weekday = iota, with iota now 1. Because the expression is repeated, it can be any constant expression, not just iota:
iota counts lines, not names: two constants on the same line share one iota value, as X and Y show.
Starting at 1, and Why You Might Not
A variable of an enum type that nobody set holds 0, its zero value. If 0 is a real value like Sunday, you cannot tell "the user chose Sunday" from "the field was never filled in". Three common fixes:
Option 1 is the most common in production code, and protobuf-generated Go enums follow it (..._UNSPECIFIED = 0). The zero value then means something honest.
Skipping Values
The blank identifier _ consumes an iota value without creating a name. Use it to leave gaps, for example to match numbers defined by a protocol or to retire a value without renumbering the rest:
type Opcode byte
const (
OpContinue Opcode = iota // 0
OpText // 1
OpBinary // 2
_ // 3, reserved
_ // 4, reserved
_ // 5, reserved
_ // 6, reserved
_ // 7, reserved
OpClose // 8
OpPing // 9
OpPong // 10
)
When the numbers are fixed by an external spec, as with these WebSocket opcodes, writing them out explicitly (OpClose Opcode = 8) is often clearer than counting blanks. iota is for values whose exact numbers you do not care about.
Never reorder or insert into an iota list whose numbers are stored in a database, a file, or sent over the network. Adding a line in the middle shifts every value after it. Append new values at the end, or assign numbers explicitly.
Adding a String Method
Give the type a String() string method and fmt uses it for %v, %s and Println:
Two details in that method matter:
- The bounds check. Without it,
Weekday(9).String()panics with an index out of range, and it will happen eventually, because nothing stops a caller from creatingWeekday(9). - The
int(d)insideSprintf. Formattingditself with%dis fine, but formatting it with%vwould callString()again and recurse until the stack overflows.
%d still prints the number, so you get both forms: Wednesday is day 3.
Generating String with stringer
For long lists, the stringer tool writes the method for you:
//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Weekday
go generate ./...
It creates weekday_string.go with a compact String() implementation, plus a compile-time check that breaks the build if the constants change without regenerating. The -linecomment flag uses a trailing comment as the name, which is handy for names with spaces.
Validating Values
A Go enum is not closed. Any value of the underlying type converts to it, and untyped constants convert implicitly:
var d Weekday = 42 // compiles
d = Weekday(userInput) // compiles
So check values that come from outside your code (JSON, databases, flags, other packages):
The unexported colorCount sentinel at the end of the block keeps IsValid correct when you append new colors, since it always sits one past the last real value.
Switching on an Enum
Enums are usually consumed by a switch. Go does not check that a switch covers every value, so add a default that reports the surprise:
func (c Color) Hex() string {
switch c {
case Red:
return "#ff0000"
case Green:
return "#00ff00"
case Blue:
return "#0000ff"
default:
return "#000000"
}
}
The third-party exhaustive linter (included in golangci-lint) reports switches on enum types that miss a case, which gets you most of what an exhaustive enum check gives in other languages.
Bit Flag Enums
When values combine, like permissions, use one bit per value with 1 << iota:
| combines flags, & tests them, and &^ (Go's AND NOT operator) clears them. An unsigned underlying type is the right choice here: uint8 holds 8 flags, uint64 holds 64.
String Enums
When the value is stored or sent as text anyway, a string-based type avoids the conversion layer:
type Env string
const (
EnvDev Env = "dev"
EnvStaging Env = "staging"
EnvProd Env = "prod"
)
Values print and serialize readably with no String() method, and a database column holds "prod" instead of a number that depends on declaration order. The trade-offs: comparisons are string comparisons, bit flags are impossible, and validation still falls on you, since Env("banana") compiles too.
Enums and JSON
An integer enum serializes as a number by default. To read and write names instead, implement encoding.TextMarshaler and encoding.TextUnmarshaler. encoding/json uses them for values and for map keys:
MarshalText has a value receiver so it works on both Level and *Level; UnmarshalText needs a pointer receiver because it changes the value. The same two methods make the type work with the flag package's TextVar and with most config libraries.
Gotchas
- Implicit conversion of literals. A function taking a
Weekdayalso accepts the untyped constant42. Only typed values of another type are rejected. - Forgetting the type on the first line. In
const ( Red = iota; Green; Blue )all three are untyped integer constants, notColorvalues, so methods onColordo not apply to them. WriteRed Color = iotaso the repeated expression carries the type. - Reordering stored enums. Inserting a value in the middle of an
iotablock silently changes the numbers already saved elsewhere. - Recursion in String. Inside
String(), never format the receiver with%vor%s. Convert to the underlying type first.
Frequently Asked Questions
Does Go have enums?
Not as a language feature. There is no enum keyword. The idiomatic replacement is a named type plus a block of typed constants, usually numbered with iota:
type Color int
const (
Red Color = iota
Green
Blue
)
The type gives you readable signatures and a place to hang methods like String(). It does not stop someone from writing Color(42), so validate values that come from outside.
What is iota in Go?
iota is a predeclared identifier that equals the index of the current line (constant spec) inside a const block, starting at 0. It resets to 0 in every new const block. When a line omits its expression, Go repeats the previous expression with the next iota, which is what makes Red = iota; Green; Blue produce 0, 1, 2.
How do I make iota start at 1?
Write First Kind = iota + 1 on the first line, or skip zero with a blank identifier: _ = iota then First. Many Go programmers instead keep 0 and name it Unknown or Invalid, so an uninitialized variable (whose zero value is 0) is clearly not a real choice.
How do I print an enum as a string in Go?
Give the type a String() string method. fmt calls it for %v, %s and Println, so fmt.Println(Green) prints Green instead of 1. You can write the method by hand with a switch or an array, or generate it with go run golang.org/x/tools/cmd/stringer@latest -type=Color.
How do I convert a string to an enum in Go?
Write a parse function that looks the string up, usually in a map[string]Color or a switch, and returns an error for unknown input: func ParseColor(s string) (Color, error). Implementing UnmarshalText with the same logic makes JSON, flags and config loaders use it automatically.