top of page

Golang Enum: A Practical Guide

Enums, short for enumerations, are a way to represent a fixed set of related constants with distinct values. They’re commonly used to define a specific range of options or states within a program, for example, days of the week, HTTP status codes, or different roles in an application.


Golang enum doesn’t have built-in enum types like other languages (e.g., C# or Java). However, Go provides alternative approaches to achieve similar functionality. Developers often use constants or custom types to emulate enums. Let’s explore those alternatives!

Golang Enum: A Practical Guide

This guide will give you the knowledge to emulate enum in Golang applications. We'll explore two main approaches: using constants with iota and creating custom types. By the end, you'll be well-versed in leveraging enums to boost the clarity and efficiency of your Go code.


To proceed, it's important to grasp the difference between Go's approach to Golang enums and native enums:


Differences between Golang enum and native enum

Let’s compare Go’s approach to native enums:

Aspect

Go Enums (Emulated)

Native Enums (Other Languages)

Syntax

Constants or custom types

Explicit enum keyword

Underlying Type

Integers (often int) or strings

Enumerated type (e.g., enum class)

Automatic Increment

Yes (using iota)

Yes (sequential values)

Explicit Values

Possible (assign specific values)

Yes (assign values explicitly)

Type Safety

Limited (constants are untyped)

Strong (enum type checks)

String Representation

Custom methods (e.g., String())

Built-in (string representation)

Use Cases

Status codes, options, domain values

Same as Go, plus more expressive


Emulating Enums in Go

In Go (Golang), enumerations (enums) are not directly built into the language. However, developers can achieve enum-like behavior using alternative approaches. Let’s explore these alternatives:


Defining Golang Enum as Constants:

Constraints in traditional enums often refer to restrictions placed on the allowed values or associated data types. In Golang's emulated enums with constants, constraints are achieved through a combination of:

  • Golang Type Safety: Defining the Golang enum as a constant type (e.g., int or string) inherently restricts the variable's type. It can only hold values defined within the enum itself.

  • Limited Control with iota: Using iota for constant assignments offers a basic level of constraint by assigning sequential values. This helps prevent assigning unrelated numbers outside the defined range. However, it doesn't enforce specific value names.


Defining Golang constants with iota:

STEP 1: Declare the Enum Type:

const ( // Your enum options here )

STEP 2: Assign Initial Value (Optional):

const ( 
	Sunday = 0 // Initial value for the first option (optional) 
	Monday = iota 
	Tuesday // ... 
)

Assign the desired initial value (often 0) to the first constant if using integer values.


STEP 3: Define Enum Options with iota:

const ( 
	Sunday = 0 // Initial value for the first option (optional) 
	Monday = iota 
	Tuesday 
	Wednesday // ... 
)

  • Create constants with descriptive names representing your enum options.

  • iota automatically assigns sequential integer values (or string literals if using string type) to each subsequent constant.


Example:

const (
  Sunday = 0
  Monday = iota
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)

// This is valid because the variable is of type Day and can only hold values defined in the enum
var today Day = Monday

// This would result in a compile-time error because the value is not part of the enum
// var invalidDay Day = 10

Golang Custom Types:

Golang allows you to create custom types to emulate enums, offering more flexibility and type safety compared to using constants with iota.


STEP 1: Define the Golang Custom Types:

  • Declare a new type using the type keyword followed by a name (often PascalCase like DayOfWeek).

  • Set the underlying type within parentheses (usually int or string for enum values).


STEP 2: Define Enum in Golang with Options as Unexported Constants:

  • Create constants with descriptive names representing your enum options, inside the custom type definition.

  • Make these constants unexported (private) using lowercase names to prevent accidental usage outside the type.

  • Assign the desired integer or string values to these constants.


Example:

type DayOfWeek int

const (
  sunday DayOfWeek = iota // Unexported constant
  monday
  tuesday
  // ...
)
  • We define a custom type DayOfWeek with an underlying type int.

  • Inside the type definition, we create unexported constants like Sunday, Monday, etc., representing the days of the week.

  • Iota is still used here, but its values are assigned to the unexported constants for internal use.


Benefits:

  • Golang Type Safety: The variable's type enforces can only hold values defined within the enum.

  • Encapsulation: Unexported constants within the type hide implementation details and prevent misuse.

  • Flexibility: You can add methods to the custom type to define additional behavior specific to the enum.


Example Usage:

var today DayOfWeek = monday

fmt.Println(today) // Prints "monday"

// This would result in a compile-time error because "Wednesday" is not a public constant
// fmt.Println(Wednesday) 

Choosing When to Use:

This approach is ideal for complex enums where you might need:

  • Additional methods associated with the enum values (e.g., converting to string).

  • Stricter-type safety to prevent accidental usage of invalid values.


String Representation:

By default, Custom type Go enum is their underlying integer value (e.g., 0 for Sunday). Implementing a String method allows you to convert these internal values to human-readable strings ("Sunday"). This improves code readability and makes it easier to work with enums.


Example with String Method:

type DayOfWeek int

const (
  sunday DayOfWeek = iota // Unexported constant
  monday
  tuesday
  // ...
)

func (d DayOfWeek) String() string {
  days := [...]string{"Sunday", "Monday", "Tuesday", /* ... */}
  if d < 0 || d >= len(days) {
    return "Invalid DayOfWeek"
  }
  return days[d]
}

day := monday

fmt.Println(day)       // Prints "0" (underlying integer value)
fmt.Println(day.String()) // Prints "Monday" (string representation)
  1. We define a String method on the DayOfWeek type.

  2. Inside the method:

  • We create slice days containing the string representations of each day.

  • We check if the DayOfWeek value is within the valid range of the slice index.

  • If valid, we return the corresponding string from the day's slice using the value as the index.

  • If invalid, we return a message indicating an "Invalid DayOfWeek".


Benefits:

  • Improves code readability by displaying human-readable strings.

  • It simplifies working with enums in functions that expect strings.


When to use enums in Golang?

Knowing when to use enum in Golang is essential for writing clean, maintainable code. Here are some scenarios where enums are handy:

  1. Status Codes and Flags: Use Golang enums to represent status codes (e.g., success, error, warning) or flags (on/off, enabled/disabled). Example: Define an enum for HTTP status codes (200, 404, 500) or user roles (admin, user, guest).

  2. Options and Configuration: When you have a finite set of options to handle enums in different cases. Example: Enum for sorting options (ascending, descending) or logging levels (debug, info, error).

  3. Domain-Specific Values: Enums help express domain-specific concepts clearly—for example, Days of the week (Monday to Sunday), and months (January to December).

  4. State Machines and Finite State Transitions: Represent states and transitions in state machines using enums. Example: A traffic light state machine (red, yellow, green).

  5. Type Safety and Readability: Custom enum types enhance type safety and make code more readable. Example: Define a Season enum (Spring, Summer, Autumn, Winter) instead of using raw integers.


Benefits of using enums in Golang

  1. Golang enum make code more readable by replacing cryptic integer or string literals with clear and descriptive names.

  2. Enums enforce type safety by restricting variables to a predefined set of values. This reduces the risk of runtime errors caused by typos or accidental assignment of invalid values.

  3. By using enums for domain-specific concepts, you prevent errors caused by using incorrect values. For example, an OrderStatus enum ensures you only use valid states like "pending" or "shipped" instead of typos or unexpected strings.


Limitations of using enums in Golang

  1. Offers less flexibility. You can't easily add methods or behaviors directly to enums defined with constants and iota.

  2. Using constants and iota might introduce unnecessary boilerplate code compared to using raw literals for simple enums with just a few options.

  3. Adding new options later might require code changes in multiple places, impacting maintainability.


Golang Enum Best Practices

  • Clear and Descriptive Names:  Use meaningful names for the enum type (e.g., WeekDay, OrderStatus) and its options (e.g., Sunday, Monday, Pending, Shipped). This improves code readability and maintainability.

  • Consistent Naming Conventions: Maintain consistency in naming conventions for enum types and options. Common approaches include PascalCase (e.g., OrderStatus) or snake_case (e.g., order_status).

  • Avoid Magic Numbers: Eliminate raw integer or string literals scattered throughout your code. Enums centralize these values, making them easier to manage and update.

  • Handle Edge Cases: Consider all potential scenarios when defining your enum. If new options become necessary, ensure the design allows for easy expansion.

  • Document Your Enums: Provide clear comments explaining the purpose of the enum, its allowed values, and any relevant usage guidelines. Documentation helps other developers understand your code.

  • Choose the Right Approach: Constants with iota are for simple cases with few options. Consider custom types enums of Golang for complex enums requiring additional behavior or type safety.

  • Leverage Tools: Utilize tools like the stringer package to generate string conversion methods for your custom-type enums. This simplifies interacting with the enum values as strings.


Conclusion

While Go doesn't offer native enums, the techniques covered in this article - using constants with iota and creating custom types - provide effective workarounds. Remember, Golang enums (emulated enums) excel when representing a well-defined set of values within your program's domain. By leveraging these techniques, you can significantly improve the readability, maintainability, and type safety of Golang code.

1 Comment


Nikolas
Nikolas
3 days ago

This post was a great read. Very informative and interesting. Solar

Like
bottom of page