Go: What is Flags Enum and How to Implement It
In C#, a flags enum lets you mix multiple enum values using bitwise operations, it's a unique form of enum.
The [Flags] attribute in C# signals that you can treat an enum as either a set of flags or a bit field. For example:
[Flags]
enum TextFormats {
Bold = 1,
Italic = 2,
Underline = 4
}
Go, on the other hand, doesn’t have built-in support for this type of enum. If you want similar features in Go, you’ll have to use bitwise operations and something called iota to make your own flags enum.
How flag enum works?
If flag enums are old news to you, feel free to jump ahead.
Let’s break down how this actually operates, using the earlier example for context:
[Flags]
enum TextFormats {
Bold = 1, // 001
Italic = 2, // 010
Underline = 4 // 100
}
In this specific example, the value attributed to Bold
is 1, which in binary format is 0001
. Similarly, Italic
has been assigned the number 2, corresponding to 0010
in binary, and Underline
is designated with 4, equivalent to 0100
in binary.
If we combine the Bold
and Italic
enums using the ‘|’ operator and when you do that, the outcome would be Bold | Italic
, resulting in a binary value of 011
and a decimal value of 3 (derived from 2⁰+ 2¹).
To sum up: Bold
is represented by the number 1, Italic
by 2, and the combination of Bold
and Italic
gives you 3. It’s an intuitive way to handle multiple options, thanks to bitwise operations.
However, be cautious that adding more keys to your enum will escalate the values quickly.
Flags Enum In Go
In Go, we can use the iota
keyword together with the shift left operator to mimic the behavior of a flags enum:
const (
Bold = 1 << iota // 1 (001)
Italic // 2 (010)
Underline // 4 (100)
)
func main() {
formatTypes := Bold | Italic
fmt.Println(formatTypes) // 3 (011)
}
“Is that all there is to it?”
No, let’s also add utility functions that make the flags enum easier to handle:
Has(flags int, value int)
: Checks if a particular value is present within the flags, “Does the format variable have the Bold value set?”
func Has(set, flag int) bool {
return set&flag == flag
}
Add(flags int, value int)
: Adds a given value to the flags enum, similar to the example above.
func Add(flags int, value int) int {
return flags | value
}
Remove(flags int, value int)
: Deletes a specific value from the flags enum. If you’re new to bitwise operations, this might require some getting used to.
func Remove(flags int, value int) int {
return flags &^ value
}
“What about method chaining like text.Has(Bold), text.Add(Bold), text.Remove(Italic)?”
We’ll cover that in the next section, where we’ll be building our own bitwise toolset.
Getting a Handle on Flags Enum
So, we’re going to establish a new type called ‘Bitwise’ and shift those global functions to become methods for this new type, here’s what it looks like:
type Bitwise int
func (set Bitwise) Has(flag Bitwise) bool {
return set&flag == flag
}
func (set Bitwise) Remove(flag Bitwise) Bitwise {
return set &^ flag
}
func (set Bitwise) Add(flag Bitwise) Bitwise {
return set | flag
}
With this structure, you can use the methods in a chain-like manner:
func main() {
formatTypes := Underline | Italic
formatTypes = formatTypes.Add(Bold).Remove(Underline)
fmt.Println(formatTypes) // 3: Bold | Italic
}
And one more thing, if you’re not keen on crafting your own simple toolkit, the math/bits
package in Go can help simplify these bitwise operations
Bitwise technique is somewhat familiar with who participate in competitive programming. But the iota part I haven't known yet, great post btw.
Nice! I had not considered using left shift to emulate Flags, so this article was very insightful.