iota and Enumerations
iota numbers the lines of a const block, which is how Go builds enum-like sets, and String() makes them print readably.
const ( A Type = iota; B; C ) // A is 0, B is 1, C is 2Programs often need a small fixed set of named values: log levels, order states, permission bits. Go has no special enum keyword. It uses a const block and iota instead.
How iota works
iota is an untyped integer constant whose value is the position of the line inside the current const block. On the first line it is 0, on the second 1, and so on. It resets to 0 at the start of each new const block.
A const block has one more trick. If a line gives a name but no value, it repeats the previous line's expression. So writing = iota once at the top and then listing bare names gives you 0, 1, 2, 3 without typing a single number.
Giving the set a type and a name
Declare your own integer type, such as type Level int, and use it on the first line of the block. Now the values are Levels rather than loose numbers, and a function that takes a Level will not accept an unrelated count.
By default such a value prints as a bare number. Add a method called String that returns a string. A method is a function attached to a type. The fmt package looks for exactly this method, described by the interface fmt.Stringer, and calls it when printing the value.
What trips people up
The numbers come from line positions, so the order of the lines is part of your data.
Example
package main
import "fmt"
// Level is an integer type of its own, so a Level cannot be mixed up
// with an ordinary number.
type Level int
const (
Debug Level = iota // Debug is 0, and each later line counts up by one
Info
Warn
Error
)
// levelNames holds one name per Level, numbered from 0.
var levelNames = [...]string{"DEBUG", "INFO", "WARN", "ERROR"}
// String is a method: a function attached to the Level type.
// fmt calls it when it prints a Level.
func (l Level) String() string {
return levelNames[l]
}
func main() {
fmt.Println(Debug, Warn)
// %d asks for the number, so String is not used. %v uses String.
fmt.Printf("%d %v\n", Error, Error)
}
// Output:
// DEBUG WARN
// 3 ERRORWhen to use it
- Numbering log levels or order states without typing the numbers by hand.
- Building a set of permission bits that can be combined into one value.
- Making a status print as a readable word in logs instead of a bare number.
- Reserving 0 as an explicit "unknown" so an unset field is detectable.
More examples
Permission bits that combine
Each value takes a separate bit, so several of them can be held in one number at once and tested individually. This is the shape used by file modes and feature flags. The bitwise operators that combine and test them have their own lesson.
package main
import "fmt"
type Perm uint8
// 1 << iota doubles on each line: 1, then 2, then 4.
const (
Read Perm = 1 << iota
Write
Exec
)
func main() {
fmt.Println(Read, Write, Exec)
}
// Output:
// 1 2 4Skipping a value with the blank identifier
The name _ is the blank identifier: it accepts a value and discards it. Here it burns iota 0 so that KB lands on iota 1. Use this whenever the counting is right but the first slot is not wanted.
package main
import "fmt"
const (
_ = iota // throw the 0 away: there is no useful "0 bytes" unit
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
)
func main() {
fmt.Println(KB, MB, GB)
}
// Output:
// 1024 1048576 1073741824Starting at 1 so zero means "not set"
A struct field of type Status starts at its zero value, 0. Naming 0 StatusUnknown means a field nobody filled in reads as unknown rather than silently claiming to be pending. Do this whenever the value may arrive from decoded JSON or a database row.
package order
type Status int
const (
StatusUnknown Status = iota // 0, which is what an unset Status holds
StatusPending
StatusPaid
StatusShipped
)
Discussion