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.
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.
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.
func updateOrder(id int, status OrderStatus) { /* ... */ }
updateOrder(42, Shipped)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:
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 1For 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":
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:
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 |:
type Permission uint8
const (
Read Permission = 1 << iota // 1
Write // 2
Execute // 4
)
perm := Read | Write
fmt.Println(perm&Write != 0) // trueString enums
When values cross a boundary (JSON APIs, database columns, logs), string constants are often clearer and stay stable if you reorder them:
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.
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
const block + iota.String() method (or generate it) so logs are readable.iota expressions build sizes and bit flags.Last updated on