loading...

Enums in Go - why and how?

ineedale profile image Alex Leonhardt ・2 min read

Go doesn't (yet) have enums as such, but it seems common to use constants with iota for this instead.

Passing enums instead of strings to a constructor helps reduce possible bugs; when a constructor accepts a set of possible options for an argument, it's easy to "fat finger" (i.e. misspell) them.

Using custom "enums" helps to prevent this and also documents available options for that argument.

Here's what I found on stackoverflow on the why (above), and here's an example I've implemented.

https://github.com/alex-leonhardt/go-passing-enums/

main.go

package main

import (
    "fmt"

    "github.com/alex-leonhardt/go-passing-enums/pkg/config"
)

func main() {
    cfg, err := config.New(config.Env)
    fmt.Printf("%#v %#v\n", cfg, err)
    cfg, err = config.New(config.File)
    fmt.Printf("%#v %#v\n", cfg, err)
    cfg, err = config.New(42)
    fmt.Printf("%#v %#v\n", cfg, err)
}

we're using the New() constructor function in the config package and pass it the config type to return

pkg/config/config.go

package config

import (
    "errors"

    "github.com/alex-leonhardt/go-passing-enums/pkg/config/env"
    "github.com/alex-leonhardt/go-passing-enums/pkg/config/file"
)

// Configurer describes a config provider
type Configurer interface {
    Get(string) string
    Set(string) bool
    Del(string) bool
}

// T is used to select the type of config to return
type T uint

// Constants for T
const (
    Env T = iota
    File
    Unknown
)

// New takes a config.T type and returns an Configurer
func New(t T) (Configurer, error) {
    switch t {
    case Env:
        return env.New(), nil
    case File:
        return file.New(), nil
    default:
        return nil, errors.New("eh?")
    }
}

we're specifying T as a uint which we'll use as our custom "enum" to pass to New(), depending on the "type", a different Configurer is being initialised (using its own New() constructor) and then returned

pkg/config/env/env.go

package env

type env struct {
    val1 string
    val2 int
    val3 bool
}

// New creates a new config
func New() *env {
    return &env{}
}

func (c *env) Get(v string) string {
    return ""
}
func (c *env) Set(v string) bool {
    return true
}
func (c *env) Del(v string) bool {
    return true
}

finally, New() returns a pointer to an initialised env struct which satisfies the config.Configurer interface by implementing the required methods Get, Set and Del.

Hope this helps anyone, it helped me already just by writing this!

If you have any comments, suggestions, better explanations, please do leave a comment. Always happy to learn new things!

Posted on by:

ineedale profile

Alex Leonhardt

@ineedale

Interested in all things #SRE, Distributed Systems, #Observability, System Design, #Automation / DevOps, #Go and #Python

Discussion

markdown guide
 

Guys from Uber recommends to start enum from 1, zero is default(not initialized) value, in this case, you no need "unknown" value

github.com/uber-go/guide/blob/mast...

 

Thanks! That's actually a good point, although there are valid cases for starting at 0.

Thinking about it, defaulting to 0 in the example case above is probably actually a valid thing to do.

Nevertheless, thanks for the link and tip! I'll have a read through what Uber does and try to learn from them too.