CyberSpy

Rantings from a guy with way too much free time

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!

comments powered by Disqus