gotcha iota

I ran into a curious little gotcha with Go’s iota construct today and I wanted to share it with you.

Without cheating, what would you expect the following code to output?

package main

import "fmt"

const (
    one = 1 << iota
    two
)

func main() {
    fmt.Println(one, two)
}

If you said 1 2, you’d be correct! There’s nothing fishy going on here.

Now let’s suppose we need to add another constant. As it’s important and we want it to be visible, we’ve added it to the top of our const block:

package main

import "fmt"

const (
    greeting = "Hello, Enums!"

    one = 1 << iota
    two
)

func main() {
    fmt.Println(one, two)
}

What would you expect the program to output now?

If you said 1 2, you’d now be wrong! The output of this program is now 2 4. iota has been incremented, so one is left-shifted by 2 instead of 1, throwing all subsequent enum values out.

Without proper unit tests, this is the kind of subtle bug that could take down a production application, or worse, not take it down.

When you understand the ConstSpec, it becomes clearer why this is the case but as greeting isn’t logically part of our enumeration (or even an integer), I would have expected iota to behave differently in this case.

The only correct fix in this example is to move the new constant out of the enumeration const block and into a new const block. This is because each const block resets iota, essentially grouping the constants together, regardless of type and the order in which they appear.