Go Get Interfaced: Enums in Golang
Golang, Interfaces, and Enums
So here’s a nice golang idiom that I ran across years ago that I found generally useful. Golang, unlike languages like c doesn’t natively support enumerations. Instead, constants typically are used when creating a list of enumerations. But, go is a strongly-typed language, so we can do better than simply using constants - we can type our enumeration with the use of a type
declaration.
type DogState uint
By defining a type for our enumeration, we can consistently pass and return typed-values among our functions operating on our enumeration. Okay, that’s all well and good, but what happens when we want to go between uint
values and string
representations of those values; a commonly used paradigm when working with enumerations. One approach would have us write mapping functions that might switch
among the values to return a string
. Or, when going from a string
name of an enumeration to the uint
value, we might use a map[string]
. That’s a doable implementation, but there’s an easier, and more idiomatic way.
Interfaces
A more elegant way is to use interfaces. Interfaces are contracts that tell us what function signatures must be implemented in order to fulfill the interface contract. We can create an Enum
interface that we use for all of our enumerations.
type Enum interface {
name() string
ordinal() int
values() *[]string
}
This interface specifies three functions name()
, ordinal()
, and values()
. Notice that there is no arguments being passed to any of the functions. At first glance, you may be wondering how we get the string
value of an enum if we’re not passing name()
a unt
. The magic
comes when we define a type (DogState
for example), and then implement the Enum
interface on that type. Let’s do that and see how it works.
Implementing Enum
on DogState
We merely need only to implement three functions to fully satisfy the Enum
interface on DogState
as well as define a few constants that enumerate our DogState
, and an array of strings associated with their names:
const(
ALIVE = iota
PUPPY
ADULT
DEAD
)
var dogTypeStrings = []string{
"ALIVE",
"PUPPY",
"ADULT",
"DEAD",
}
Now to implement our interfaces, we create method functions on our type.
func (dt DogType) name() string {
return dogTypeStrings[dt]
}
func (dt DogType) ordinal() int {
return int(dt)
}
func (dt DogType) values() *[]string {
return &dogTypeStrings
}
Now that we’ve implemented the Enum
interface on DogType
, we can easily go between uint
and strings
in our code:
var ds DogState = DEAD
fmt.Printf("I'm sorry to say your dog is %s\n", ds.name())
or to get an entire list of all states:
var ds DogState
for ordinal, s := range ds.values() {
fmt.Printf("%s, %d\n", s, ordinal)
}
or return the ordinal of the enumeration from the list:
var ds DogState = ALIVE
fmt.Printf("dogstate ordinal is %d\n", ds.ordinal())
Using interfaces for enumerations in go is a convenient and clean way of using type-safe constants. Enjoy!