This post will describe how to implement and use enumerations (or enum types) in Go.

banner image

Enums are types that contain only a limited number of fixed values, as opposed to types like int or string which can have a wide range of values.

This is useful for many situations: For example, when describing seasons in temperate climates, you’ll want to limit the options to certain possible values: Summer, Winter, Spring and Autumn.

enums contain only a limited set of values

Let’s look at different ways of implementing this type of data:

Defining Enums as Constants

The simplest way to implement enums for season options is to create an int or string constant for each season:

// int mapping
const (
	Summer int = 0
	Autumn     = 1
	Winter     = 2
	Spring     = 3
)

// string mapping
const (
	Summer string = "summer"
	Autumn        = "autumn"
	Winter        = "winter"
	Spring        = "spring"
)

While this would work for small codebases, we will face a few immediate issues:

  1. It’s easy to make mistakes in your code. A developer can make the mistake of using integers outside the range of the ones defined.

  2. Definitions from unrelated enums can overlap and cause conflicts:

     const (
         Summer int = 0
         Autumn     = 1
         Winter     = 2
         Spring     = 3
     )
    
     const (
         Apples  = 0
         Oranges = 1
     )
    
     func main() {
         // Ideally, this should never be true!
         fmt.Println(Summer == Apples)
     }
    
  3. This is semantically incorrect - Seasons are not really integers or strings, they’re seasons!

Enum Types

We can define a new Season type with int as the base type:

type Season int64

const (
	Summer Season = 0
	Autumn Season = 1
	Winter Season = 2
	Spring Season = 3
)

This defines the above constants as their own types. You can also use the iota identifier to achieve the same result in a more readable manner:

const (
	Summer Season = iota
	Autumn
	Winter
	Spring
)

Using iota instead of manually assigning values also prevents you from inadvertently assigning the same value to more than one constant, which could otherwise cause a conflict. It also carries over the type, so we don’t have to declare Season every time.

Since Season is a new type, you can’t compare it to other types anymore:

func printSeason(s Season) {
	fmt.Println("season: ", s)
}

func main() {
	i := 0
	printSeason(i)
	// This will result in a compilation error since
	// i is an int, and `printSeason` only accepts
	// Season types
}

It’s now impossible to compare Season constants with Fruit constants, or use them interchangeably.

Adding String Methods to Enum Types

In the previous example, if we call printSeason(Winter), we will get the output:

season:  2

Try this on the Go playground

It would be better if we can a more human-readable format when printing our season selection.

Let’s implement the String method for the Season type to achieve this:

const (
	Summer Season = iota
	Autumn
	Winter
	Spring
)

func (s Season) String() string {
	switch s {
	case Summer:
		return "summer"
	case Autumn:
		return "autumn"
	case Winter:
		return "winter"
	case Spring:
		return "spring"
	}
	return "unknown"
}

If we run the code now, we should get the output:

season:  winter

We can add any number of methods to this type as we require.

Default Enum Values

In our previous examples, we defined our Season constants as integer values. This means that 0 (which is Summer) would be the default value.

When using enum types, you should make sure that you’re ok with the default value. For example, if you want the value to be set explicitly, it’s better to add a custom “unknown” or “undefined” value:

const (
	// since iota starts with 0, the first value
	// defined here will be the default
	Undefined Season = iota
	Summer
	Autumn
	Winter
	Spring
)

This can be useful when you want to check if a value has been set or not:

if mySeason == Undefined {
	// log an error
}
Test your knowledge: How would you implement a default string enum?

If we were using string enums (type Season string) instead of ints, how would you rewrite the constant definition for the Undefined value?

Since the default value of a string in Go is the empty string (""), this is what your default enum should be assigned to as well.

Using Enums in Practice

Now that you know how to implement enums, the next question is: When to use them?

We can see enums used throughout the standard library. Some common examples are:

  1. Prefix flags in the log package
  2. Status codes and methods in the http package
  3. Standard grayscale colors in the color package

In general, you should use enums whenever you have group of limited, and related values for a particular type.

Have you used enums, or something similar to enums in your code before? How was is useful? Let me know in the comments!