Menu

Golang Enum: How to Build Enums with const and iota

Go has no enum keyword. This page shows the idiomatic replacement: a named type plus a const block with iota, and how to add String(), validation, parsing, bit flags and JSON support.

This page includes runnable editors - edit, run, and see output instantly.

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:

  1. iota equals the index of the current line in the block, starting at 0, and resets to 0 in each new const block.
  2. A line with no = expression repeats the previous line's expression and type, evaluated with the new iota.

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 creating Weekday(9).
  • The int(d) inside Sprintf. Formatting d itself with %d is fine, but formatting it with %v would call String() 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 Weekday also accepts the untyped constant 42. 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, not Color values, so methods on Color do not apply to them. Write Red Color = iota so the repeated expression carries the type.
  • Reordering stored enums. Inserting a value in the middle of an iota block silently changes the numbers already saved elsewhere.
  • Recursion in String. Inside String(), never format the receiver with %v or %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.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED