When to use Go's iota

❝Ways of using iota without causing trouble.❞
Contents

TL;DR Don’t use iota for defining constants where its values are explicitly defined (elsewhere). For example, when implementing parts of a specification and the specification says which values are assigned to which constants, you should explicitly write the constant values. Use iota for “internal” purposes only. That is, where the constants are referred to by name rather than by value. That way you can optimally enjoy iota by inserting new constants at any moment in time / location in the list without the risk of breaking everything.

Go (golang) has a special keyword iota. It is a very general construct that is used as an automatically incrementing counter in const declaration blocks. With this automatically incrementing counter, it gives you a “short-hand” notation for assigning values to constants. In particular, this is used to define enumerations. Additionally, with the start of every const block, iota gets reset to 0, so it can be used repeatedly.

The Go language specification and Effective Go: Constants explain how to use iota.

Consider the following example:

package main

import "fmt"

type State uint

const (
	StateCreated State = iota  // 0
	StateInitialized           // 1
	StateRunning               // 2
	StateStopped               // 3
)

func main() {
	var state = StateRunning
	fmt.Printf("State: %v\n", state) // prints State: 2
}

We define a type State based on type uint. To actually make use of State, we should define some instants. This is done within the const block. States Created, Initialized, Running and Stopped are defined. iota starts its life at 0 and increments on every next line.

Note that Go has the notion of a zero-value. All variables and struct fields that are not explicitly initialized to some value will get their type’s zero-value. When defining an enumeration in Go, you should keep this in mind. In the example above, I have done so by distinguishing between states Created and Initialized. Without any explicit initialization, the zero-value gets assigned, thus indicating the state Created. Once an explicit initialization is performed, the variable can be changed to Initialized. This avoids any confusion that may arise if the zero-value also has a very specific value or indicates a specific state.

Now, as for the main topic of this post. When one should use Go’s iota. I will not talk about all possible/viable applications. Instead I will try to give you an idea of when it is most convenient to use iota and one specific case of when not to use iota.

When not to use iota

A clear example of when not to use iota is for the implementation of a specification of some kind. If the specification defines constants and these constants have explicitly defined values, then you should use these values. Do not try to use iota to “juggle” it so that it outputs the correct values for all constants defined by the specification. (There are tricks such as _ = iota to make iota skip a number.) There is a reason for the specification to state these values explicitly.

The following code is a sample from the implementation of the http 2 protocol in Go, by Brad Fitzpatrick and others:

type SettingID uint16

const (
	SettingHeaderTableSize      SettingID = 0x1
	SettingEnablePush           SettingID = 0x2
	SettingMaxConcurrentStreams SettingID = 0x3
	SettingInitialWindowSize    SettingID = 0x4
	SettingMaxFrameSize         SettingID = 0x5
	SettingMaxHeaderListSize    SettingID = 0x6
)

Constants are assigned explicit values, as defined by the spec.

As soon as a constant’s value is defined explicitly in a specification and is supposed to be frozen from then on, then you should do the same in your code.

When to use iota

I prefer to use iota for purpose of “internal” use only. Use iota assignments if the value does not really matter.

For example, if you have a state machine, like the one in the first example, where you are expressing and managing internal state using a predefined set of possible states, then you can keep using the constants themselves and refer to them by their name. You would compare a state variable with a state constant’s name, not its assigned value.

if state == StateStopped {
	fmt.Println("Thingie has stopped.")
}

In the case where serialization is required, the constant’s value will become relevant. As long as the serialized value is only ever read by your own implementation (or an application using your package’s constants), then there is no risk of incorrect matching. That is, assuming that you don’t serialize constants, then change the constant definitions (e.g. order or value), and then try to deserialize your previously serialized data.

As a closing example, a sample from another part of the http 2 protocol implementation in Go:

type streamState int

const (
	stateIdle streamState = iota
	stateOpen
	stateHalfClosedLocal
	stateHalfClosedRemote
	stateResvLocal
	stateResvRemote
	stateClosed
)

This is from the same implementation as the earlier sample. Note that iota is used here. This type is used only for internal state management. State changes all the time, but is only referred to by the constant name. The constant’s value does not even matter here, as long as we can match it with one of the known constants.

Conclusion

As soon as you need to rely on a constant’s value, then iota becomes an issue.

With constants from other libraries, as long as it is only used for the purpose of expressing intention between the two libraries, e.g. via channel communication and method calls, then you are relatively safe. You get into a grey area when you start serializing data containing constants defined with iota from other libraries, as you cannot predict how these would change. However, this should not need to be of any concern.

In any case, the safest application is for internal use by your own implementation. As soon as constants are defined externally and dedicated values are specified, then you should switch to defining your variables explicitly with values matching those in the external definition.

Note that this way of using iota is not exactly a new thing. I think it is fairly common knowledge, although I have so far only heard Brad Fitzpatrick (IIRC) say it explicitly, which was in the context of specifications that document constants with their value. I have, however, noticed on a few occasions that people think of iota as “lacking” in one way or another, or calling it a poor replacement for your-favorite-language-here’s enumerations. In some of these cases this is simply due to mismatched expectations or lack of understanding of what iota actually has (or rather tries) to offer.