Enums in Go with iota (and How to Print Them)
Chapter 04 of 06

Enums in Go with iota (and How to Print Them)

Build type-safe enums with named types, const blocks and iota, add String methods, generate bit flags and choose between int and string enums.

Sabbir Ahmmed3 min read0
On this page

Go has no enum keyword. Instead, you combine a named type, a block of constants, and the special counter iota. The result is type-safe, readable and easy to print once you add a String method.

Integer enums with iota

iota starts at 0 in each const block and increases by one for every line. Lines without an explicit value repeat the previous expression, so you only write it once.

go
1
2
3
4
5
6
7
8
type OrderStatus int

const (
	Pending   OrderStatus = iota // 0
	Shipped                      // 1
	Delivered                    // 2
	Canceled                     // 3
)

The named type OrderStatus gives you type safety: a function that takes an OrderStatus documents exactly what it expects.

go
1
2
3
func updateOrder(id int, status OrderStatus) { /* ... */ }

updateOrder(42, Shipped)
Note
Untyped constants still convert implicitly, so updateOrder(42, 2) compiles. Go enums are a convention, not a closed set; validate values that come from users or databases.

Make enums printable with String()

Printing an integer enum shows a number by default, which is useless in logs. Implement the fmt.Stringer interface and fmt uses it automatically:

status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (s OrderStatus) String() string {
	switch s {
	case Pending:
		return "Pending"
	case Shipped:
		return "Shipped"
	case Delivered:
		return "Delivered"
	case Canceled:
		return "Canceled"
	}
	return fmt.Sprintf("OrderStatus(%d)", int(s))
}

fmt.Println(Pending, Delivered)  // Pending Delivered
fmt.Println(int(Delivered))      // 2
fmt.Println(OrderStatus(9))      // OrderStatus(9)
fmt.Printf("%v %d\n", Shipped, Shipped) // Shipped 1

For large enums, the stringer tool generates this method for you: add //go:generate stringer -type=OrderStatus and run go generate.

Start at 1 to catch unset values

The zero value of an int enum is 0. If 0 is a real status (Pending), you cannot tell "pending" from "forgot to set it". A common pattern reserves zero for "unknown":

go
1
2
3
4
5
const (
	StatusUnknown OrderStatus = iota // 0: zero value means "not set"
	Pending                          // 1
	Shipped                          // 2
)

iota with expressions

Because each line repeats the previous expression with the next iota, you can generate sequences:

go
1
2
3
4
5
6
7
8
type ByteSize float64

const (
	_           = iota             // skip 0
	KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
	MB                             // 1 << 20
	GB                             // 1 << 30
)

Bit flags work the same way, letting you combine options with |:

go
1
2
3
4
5
6
7
8
9
10
type Permission uint8

const (
	Read Permission = 1 << iota // 1
	Write                       // 2
	Execute                     // 4
)

perm := Read | Write
fmt.Println(perm&Write != 0) // true

String enums

When values cross a boundary (JSON APIs, database columns, logs), string constants are often clearer and stay stable if you reorder them:

go
1
2
3
4
5
6
7
8
type OrderStatusStr string

const (
	PendingStr   OrderStatusStr = "PENDING"
	ShippedStr   OrderStatusStr = "SHIPPED"
	DeliveredStr OrderStatusStr = "DELIVERED"
	CanceledStr  OrderStatusStr = "CANCELED"
)

Integer enums are compact and fast to compare; string enums are self-describing in JSON and SQL. A frequent compromise is an int enum in code with MarshalJSON/UnmarshalJSON (or MarshalText) methods that convert to and from strings.

Warning
Never store iota-based numbers in a database or API if the list might change. Inserting a new constant in the middle shifts every value after it and silently corrupts old data. Append new values at the end, or use string values.

Key takeaways

Go enums = named type + const block + iota.
Add a String() method (or generate it) so logs are readable.
Reserve the zero value for "unknown" when that distinction matters.
iota expressions build sizes and bit flags.
Prefer string values for anything persisted or sent over the wire.
#Golang#Enums#iota

Last updated on

Discussion0