A logo showing the text blog.marcnuri.com
Español
Home»Go»Enums in Go: An Alternative approach

Recent Posts

  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK
  • Connecting to an MCP Server from JavaScript using LangChain.js
  • The Future of Developer Tools: Adapting to Machine-Based Developers
  • Connecting to a Model Context Protocol (MCP) Server from Java using LangChain4j

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Enums in Go: An Alternative approach

2020-02-11 in Go tagged Go by Marc Nuri | Last updated: 2025-02-19
Versión en Español

Introduction

Go programmers often miss the built‑in, "sealed" enumerations (enum) types that languages like Java or C# provide. In Go, developers typically emulate enums by defining a new custom type and a set of related constants (often using the iota identifier).

In this post, I'll show you how to emulate enums in Go using custom types and constants and enhance them with methods and code generation.

Tip

What is an enum? An enumeration (enum) is a distinct type consisting of a set of named constants called elements or enumerators. The enum type restricts variables to only those values defined in the enumeration. Enums are useful for defining a fixed set of related values.

Why Go doesn't have enums

Go's design philosophy emphasizes simplicity and minimalism. While traditional enums provide type safety and a fixed set of values, Go opts for the flexibility of constant declarations. This approach aligns well with the language’s overall design while allowing you to define a clear set of related values.

Using constants as enums

The most common pattern to emulate enums in Go is defining a new custom type (usually based on int) and a set of related constants. The iota identifier is often used to create a sequence of related values.

Here's an example of how to define a custom type and constants to represent a limited set of programming languages:

language.go
package main

import "fmt"

type Language int

const (
    Unknown Language = iota // 0
    Go                      // 1
    Java                    // 2
    Python                  // 3
    JavaScript              // 4
)

func main() {
    var lang Language = Go
    fmt.Printf("The language is: %v\n", lang) // Output: The language is: 1
}

In this example, we define a new custom-type Language based on int and a set of related constants.

Tip

What is iota? The iota identifier is a predeclared identifier that represents successive integer constants. It resets to 0 whenever the const block is entered and increments by 1 for each subsequent constant declaration.

Advantages of using constants as enums include:

  • Type safety: The custom-type Language restricts variables to only those values defined in the enumeration.
  • Readability: Constants provide descriptive names for each value, making the code more readable.
  • Uniqueness: The iota identifier ensures that each constant is unique, preventing accidental reuse of values.
  • Maintainability: Constants are easy to update and maintain, allowing you to add or remove values as needed without needing to update each constant individually.

Adding behavior to enums with methods

While constants can represent a fixed set of related values, they lack the ability to define behavior. One advantage of declaring a custom type is that you can attach methods to it.

Here's an example of how to add a String method to the Language type to return the name of the language:

language.go
func (l Language) String() string {
  return [...]string{"Unknown", "Go", "Java", "Python", "JavaScript"}[l]
}

func main() {
  var lang Language = Go
  fmt.Printf("The language is: %v\n", lang) // Output: The language is: Go
}

This simple String() method approach makes it easy to convert the Language type to a human-readable string.

Enhancing enums with code generation

As your enum grows, maintaining the list of constants and associated methods can become cumbersome and error-prone. If you add or remove values to the enum, you must remember to update the list of constants and any associated methods accordingly.

One way to address this problem is by using code generation tools like stringer to generate the String method for your custom type automatically.

Here's how you can use stringer to generate the String method for the Language type:

Start by installing the stringer tool:

bash
go get golang.org/x/tools/cmd/stringer@latest

Then, add a //go:generate directive to your source file to run the stringer tool automatically with go generate:

language.go
package main

import "fmt"

//go:generate stringer -type=Language -linecomment
type Language int

const (
    Unknown Language = iota
    Go                      // Golang
    Java
    Python
    JavaScript
)

func main() {
  var lang Language = Go
  fmt.Printf("The language is: %v\n", lang) // Output: The language is: Golang
}

By running go generate ./... in the same directory as your source code, stringer will generate a String method for the Language type in a new language_string.go file.

bash
go generate

This approach simplifies the process of adding or removing values from your enum, as the String method is automatically updated whenever you run go generate.

Handling JSON serialization with enums

By default, if you marshal a struct containing your enum, Go will represent the enum as its underlying integer value. To customize the JSON representation of your enum, you can implement the json.Marshaler and json.Unmarshaler interfaces.

Here's an example of how to implement the json.Marshaler and json.Unmarshaler interfaces for the Language type:

language.go
import (
    "encoding/json"
    "fmt"
)

func (l Language) MarshalJSON() ([]byte, error) {
    return json.Marshal(l.String())
}

func (l *Language) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    switch s {
    case "Go":
        *l = Go
    case "Java":
        *l = Java
    case "Python":
        *l = Python
    case "JavaScript":
        *l = JavaScript
    default:
        *l = Unknown
    }
    return nil
}

The MarshalJSON method is used by the JSON encoder to serialize the Language type. The UnmarshalJSON method is used by the JSON decoder to deserialize the Language type.

By implementing these methods, you can control how your enum is represented in JSON format.

Limitations and trade-offs

While using constants as enums is a common pattern in Go, it has some limitations:

  • Type Safety: Despite having a distinct type, nothing stops a programmer from casting an arbitrary integer to your enum type.
  • Exhaustiveness: There's no built-in way to ensure that a switch statement covers all enum values.
  • Verbosity: Defining a custom type and constants for each enum can be verbose, especially for large enums.
  • Duplication: When additional behavior is required (like parsing from a string), you may end up writing similar code for each enum type. Check the UnmarshalJSON method in the previous section for an example.

Conclusion

While Go doesn't provide built-in enums, you can effectively emulate them using custom types and constants. By attaching methods to your custom type and using code generation tools like stringer, you can enhance your enums with additional behavior and simplify maintenance.

By following these patterns, you can effectively incorporate enum-like functionality into your Go projects, providing type safety and a clear set of related values to make your code both robust and readable.

Happy coding!

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Post navigation
Understanding Variadic Functions in GoHow to use sets in Go
© 2007 - 2025 Marc Nuri