🖐️ Declare enum in Go

introduction enum iota const

In Go, there is no enum data type known from languages such as C++, Java, or Python. However, this does not mean that enums cannot be created. If you declare constants of custom type and some helper methods, you get a structure very similar to enum in other programming languages.

See the example below to check how to declare enum in Go.

In the example, we use constants declaration with iota. Check our tutorial on how to use iota in Go.

package main

import (
	"fmt"
)

type Season int

const (
	Spring Season = iota + 1
	Summer
	Autumn
	Winter
)

func (s Season) String() string {
	seasons := [...]string{"spring", "summer", "autumn", "winter"}
	if s < Spring || s > Winter {
		return fmt.Sprintf("Season(%d)", int(s))
	}
	return seasons[s-1]
}

func (s Season) IsValid() bool {
	switch s {
	case Spring, Summer, Autumn, Winter:
		return true
	}
	return false
}

func main() {
	mySeasons := []Season{Spring, Summer, Autumn, Winter, 6}
	for _, s := range mySeasons {
		fmt.Printf("season: %s, is valid: %t\n", s, s.IsValid())
	}
}

Output:

season: spring, is valid: true
season: summer, is valid: true
season: autumn, is valid: true
season: winter, is valid: true
season: Season(6), is valid: false

How it works

  1. Declare custom type and elements of our enum
    type Season int
    
    const (
    	Spring Season = iota + 1
    	Summer
    	Autumn
    	Winter
    )
    

    As a first step, we declare new integer type Season and create successive constant values using the iota keyword.

    Why do we use int type instead of string?

    Constants are usually used to compare to a variable, e.g.

    if Spring != season { 
        // do something 
    }
    

    and often we do not need their specific values, just the fact that the constant and the variable are different. In such a case comparing two int values is faster than comparing two strings. Constants of type int also take less memory space.

    Why do we declare the custom type Season instead of using untyped constants?

    A custom type protects against passing invalid values already at the compilation stage. If we declared seasons as untyped constants, the code below of assigning invalid season value 6 to the variable would execute without any error:

    var d int = 6
    season := Spring
    season = d
    

    When using Season type, to make this code run without any error, you need to explicitly convert the d variable, so it is not possible to do it by accident:

    var d int = 6
    season := Spring
    season = Season(d)
    

    Also, a custom type of enum makes the code cleaner. For example, declaring the function

    func foo(s Season)
    

    you immediately see that the function requires season as an argument, and in the case of

    func foo(s int)
    

    you only know that it needs an integer argument, without any semantic meaning. Thanks to the custom types we also can add methods to the enum elements like String() and IsValid().

  2. Create String() method
    func (s Season) String() string {
    	seasons := [...]string{"spring", "summer", "autumn", "winter"}
    	if s < Spring || s > Winter {
    		return fmt.Sprintf("Season(%d)", int(s))
    	}
    	return seasons[s-1]
    }
    

    String() method adds the ability to print Season constants as a string rather than an int, so for the statement fmt.Println(Spring) we get spring instead of 1. For variables outside the range Spring..Winter, we print Season(X), where X is an integer value of the Season type variable.

  3. Create IsValid() method
    func (s Season) IsValid() bool {
    	switch s {
    	case Spring, Summer, Autumn, Winter:
    		return true
    	}
    	return false
    }
    

    IsValid() is a helper method that validates whether the Season type variable is one of the values Spring, Summer, Autumn, or Winter. This function should always be called for user input, to verify that the user has not passed an invalid value of the enum.

  4. main() function
    func main() {
    	mySeasons := []Season{Spring, Summer, Autumn, Winter, 6}
    	for _, s := range mySeasons {
    		fmt.Printf("season: %s, is valid: %t\n", s, s.IsValid())
    	}
    }
    

    The main() function presents what you get when you use the String() and IsValid() methods for defined values of Season constants as well as values out of range.

🧐 iota in Go - how to use?

Learn how to use iota keyword in constants declaration
introduction iota const

🖨️ Convert string to []byte or []byte to string in Go

Learn the difference between a string and a byte slice
introduction strings slice

🧠 Print the memory address of a variable in Go

Learn how to find and print the address of a variable or pointer
introduction pointer slice