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.

Syntaxconst ( A Type = iota; B; C ) // A is 0, B is 1, C is 2

Programs 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

Example · go
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 ERROR

When 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.

Example · go
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 4

Skipping 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.

Example · go
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 1073741824

Starting 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.

Example · go
package order

type Status int

const (
	StatusUnknown Status = iota // 0, which is what an unset Status holds
	StatusPending
	StatusPaid
	StatusShipped
)

Discussion

  • Be the first to comment on this lesson.