Introduction
Enums, short for enumerations, are a powerful concept in programming that allow developers to define a set of named values, each of which represents a distinct constant. Enums make code more readable, maintainable, and less error-prone by providing a clear and self-documenting way to work with a specific set of related values. While the Go programming language (often referred to as Golang) does not have a built-in enum type like some other languages, it offers a few approaches to achieve similar functionality. In this article, we'll explore how to implement enums in Go.
The Need for Enums
Enums are valuable in scenarios where you want to represent a finite set of values that have a clear relationship or significance. For example, days of the week, HTTP status codes, error types, or any other situation where the set of possible values is fixed and known in advance.
In some programming languages, like C++ or Rust, enums are a language feature, and defining them is straightforward. However, in Go, we need to use creative techniques to achieve enum-like behavior.
Using Constants
The simplest way to mimic enums in Go is by using constants. Constants provide a way to declare a fixed value that cannot be changed during the execution of the program. By defining a set of related constants, we can achieve enum-like behavior.
I have discussed the use of iota
in my previous blog.
package main
import "fmt"
// Define constants for days of the week
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
day := Wednesday
fmt.Println("Today is", day)
// output: Today is 3
}
In the code above, we use the iota keyword to automatically assign successive integer values to each constant. This creates a clear mapping between the constants and their underlying values. While this approach works, it lacks the type safety and self-contained behavior of enums in some other languages.
Using Custom Types
Another way to create enum-like behavior in Go is by using custom types and defining methods on those types. This approach allows us to encapsulate the enum values within a specific type, providing better type safety and more control over the behavior of the enum.
package main
import "fmt"
// Define a custom type for days of the week
type DayOfWeek int
// Define constants for the days of the week
const (
Sunday DayOfWeek = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
// String method to convert the enum value to a string
func (d DayOfWeek) String() string {
switch d {
case Sunday:
return "Sunday"
case Monday:
return "Monday"
case Tuesday:
return "Tuesday"
case Wednesday:
return "Wednesday"
case Thursday:
return "Thursday"
case Friday:
return "Friday"
case Saturday:
return "Saturday"
default:
return "Invalid day"
}
}
func main() {
day := Wednesday
fmt.Println("Today is", day.String())
//Output: Today is Wednesday
}
In this code, we define a custom type DayOfWeek as an integer. We also define constants for each day of the week. The String method is a receiver function for the DayOfWeek type, allowing us to convert the enum value to a string representation. This approach provides better type safety and encapsulates the enum values within the custom type.
Using Unexported Fields
Another approach to creating enums in Go is to use unexported fields and constants within a struct. This approach provides encapsulation and prevents external code from directly modifying the enum values.
package main
import "fmt"
// Define a struct to represent the enum
type DaysOfWeek struct {
Sunday int
Monday int
Tuesday int
Wednesday int
Thursday int
Friday int
Saturday int
}
// Create a variable of the struct type to represent the enum
var Days DaysOfWeek = DaysOfWeek{
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
}
func main() {
day := Days.Wednesday
fmt.Println("Today is", day)
//Output: Today is 3
}
In this approach, we define a struct DaysOfWeek with unexported fields representing the enum values. We then create a variable of this struct type to hold the enum values. Since the fields of the struct are unexported (start with a lowercase letter), they are not accessible outside the package, providing encapsulation.
Choosing the Right Approach
Each of the approaches described above has its own advantages and trade-offs. The choice of which approach to use depends on the specific requirements of your application.
Constants: This approach is simple and works well for small sets of related constants. It lacks some of the encapsulation and type safety benefits of the other approaches.
Custom Types: This approach provides better type safety and encapsulation by using methods on custom types. It's a good choice when you want to ensure that the enum values are only used in appropriate contexts.
Unexported Fields: This approach provides encapsulation, preventing external code from directly modifying enum values. It's useful when you want to ensure that the enum values are not modified outside the package.
Consider the readability, maintainability, and type safety requirements of your code when choosing the appropriate enum implementation approach.
Conclusion
Although Go does not have a built-in enum type, developers can implement enum-like behavior using various techniques. By using constants, custom types, or unexported fields, you can achieve the benefits of enums, such as improved code clarity, maintainability, and type safety. Choose the approach that best fits the needs of your application, and enjoy writing clean and robust Go code.
Top comments (4)
I was using Custom Types when I first needed enums as well, but I've since discovered Stringer and now Enumer which just makes it much easier, FWIW.
Thank @wynandpieters, I will go through this.
Thanks a lot!
Can't you create a function type and create enums as functions of this function type, enforcing types where type aliases fail?