This content originally appeared on Level Up Coding - Medium and was authored by Aiden (func25)
From using constant groups of integers to utilizing open-source libraries and the iota keyword, we’ll go over everything you need to know to effectively use enums in your Go projects
At present, Go (1.19) lacks a native enum type, unlike many other programming languages.
Despite this, imaginative Go programmers have come up with innovative methods to mimic the behavior of enums using the language’s existing tools and resources.
Let’s dive in.
The Idiomatic Way
The conventional approach is to use a set of unique integers as constants and apply them as the values of an enumeration:
type Color int
const (
Red Color = iota // 0
Blue // 1
Green // 2
)
The use of a constant group of distinct integers as the values of an enumeration is a common and typical approach in Go programming. It is generally accepted that enums should start from 1, with 0 being reserved for “undefined” or default enum values. This approach provides a clear and concise way to implement enums in Go.
In the illustration above, there is no pre-defined default value. Upon initializing the color, I prefer it not to be red, therefore we make a slight modification.
const (
Red Color = iota + 1 // 1
Blue // 2
Green // 3
)
If desired, you can include a member named “Undefined” using iota or any other default value of your choice as the first initialization for the color, such as “Black”.
const (
Undefined Color = iota // 0
Red // 1
Blue // 2
Green // 3
)
“Can you modify the sequence number to something other than the conventional one? I’m looking for something more interesting”
We have more options to extend the capabilities of the iota keyword. For instance, if you desire an enum sequence that starts from 2 and increases by increments of 2, such as 2, 4, 6, we can achieve that.
const (
Red Color = (iota + 1) * 2 // 2
Blue // 4
Green // 6
)
Since iota starts from 0, we need to add 1 to the value to avoid having Red as 0, which would result in an enum sequence of 0, 2, 4, and so on.
“Is it possible to skip a number in the sequence? I’d like to omit 2 from the enum values”
Certainly, in Go programming language, the underscore symbol _ can be utilized as a placeholder when creating an enum with the iota keyword. The use of _ as a constant name indicates to the compiler to bypass that constant and not assign a value to it.
const (
Red Color = iota + 1 // 1
_
Blue // 3
Green // 4
)
Flags Enum
This is a method for representing a collection of enumerated values as a single integer, where each bit of the integer maps to a specific enum value.
type DamageType int
const (
Poison DamageType = 1 << iota // 1
Bleeding // 2
Flame // 4
)
This allows you to store multiple enum values within a single integer variable, and to verify if a specific enum value is present in the set through the use of bitwise operations:
// create our bitwise utility, if c contains the damageType or not.
func (dt DamageType) Has(dmg DamageType) bool {
return dt&dmg == dmg
}
func main() {
damages := Poison | Flame
fmt.Println(damages.Has(Flame)) // true
fmt.Println(damages.Has(Bleeding)) // false
}
“Can you provide me with alternative tools to emulate the flag enumeration?”
Sure, you can develop your own utility, but Go also has the built-in math/bits package for bit processing. This is my custom solution for simple needs:
// Has returns a boolean indicating whether the specific flag is present in the set
func Has(set, flag int) bool {
return set&flag == flag
}
// Remove creates a new set of flags with the specific flag removed and returns it
func Remove(set, flag int) int {
return set &^ flag
}
// Add returns a new set of flags with the specific flag added
func Add(set, flag int) int {
return set | flag
}
If you’re interested, I previously wrote an article on using the flags enum approach, feel free to check it out: What is Flags Enum and How to Implement It
Go: What is Flags Enum and How to Implement It
Iterate over Enum
Currently, Go (1.19) does not offer a built-in method for iterating over an enum. However, if your enum is represented as a sequence using the iota keyword, you can use a workaround:
const (
StartColor Color = iota
Red
Blue
Green
EndColor
)
func main() {
for i := StartColor + 1; i < EndColor; i++ {
fmt.Println(i)
}
}
// 1
// 2
// 3
If you’re looking for a more customizable and flexible way to handle enums in Go, or if you’d like to use string enums, you might consider using an open-source library. One such library is go-enum, which you can use in conjunction with the go generate tool to achieve this.
You can execute the command in your terminal, or if you’re using a VSCode or an IDE that supports inline commands, simply click on it. This will generate a new file with various information about the enum Color.
In this scenario, we can perform iteration in the following way:
func main() {
for i := range ColorNames() {
fmt.Println(i, Color(i))
}
}
// 0 red
// 1 green
// 2 blue
The ColorNames() function is generated because of flag --names, there is some flags to make this easier to adapt your need, give it a try.
If you are curious about what contains in the generated file, then here is it:
// Code generated by go-enum DO NOT EDIT.
// Version:
// Revision:
// Build Date:
// Built By:
package medium
import (
"fmt"
"strings"
)
const (
// ColorRed is a Color of type Red.
ColorRed Color = iota
// ColorGreen is a Color of type Green.
ColorGreen
// ColorBlue is a Color of type Blue.
ColorBlue
)
var ErrInvalidColor = fmt.Errorf("not a valid Color, try [%s]", strings.Join(_ColorNames, ", "))
const _ColorName = "redgreenblue"
var _ColorNames = []string{
_ColorName[0:3],
_ColorName[3:8],
_ColorName[8:12],
}
// ColorNames returns a list of possible string values of Color.
func ColorNames() []string {
tmp := make([]string, len(_ColorNames))
copy(tmp, _ColorNames)
return tmp
}
var _ColorMap = map[Color]string{
ColorRed: _ColorName[0:3],
ColorGreen: _ColorName[3:8],
ColorBlue: _ColorName[8:12],
}
// String implements the Stringer interface.
func (x Color) String() string {
if str, ok := _ColorMap[x]; ok {
return str
}
return fmt.Sprintf("Color(%d)", x)
}
var _ColorValue = map[string]Color{
_ColorName[0:3]: ColorRed,
_ColorName[3:8]: ColorGreen,
_ColorName[8:12]: ColorBlue,
}
// ParseColor attempts to convert a string to a Color.
func ParseColor(name string) (Color, error) {
if x, ok := _ColorValue[name]; ok {
return x, nil
}
return Color(0), fmt.Errorf("%s is %w", name, ErrInvalidColor)
}
Next Up
If you’re interested in staying up to date with the latest happenings in the Software Engineering world, give me a follow. I’ll be sure to keep you in the loop!
Also, every 👏 helps to spread the word about my writing and I would really appreciate it.
Keep learning and have fun with it, happy coding!
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job
Go Enums: The Right Way to Implement and Iterate was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Aiden (func25)
Aiden (func25) | Sciencx (2023-02-08T15:17:30+00:00) Go Enums: The Right Way to Implement and Iterate. Retrieved from https://www.scien.cx/2023/02/08/go-enums-the-right-way-to-implement-and-iterate/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.