What is an idiomatic way of representing enums in Go?

EnumsGoGenetics

Enums Problem Overview


I'm trying to represent a simplified chromosome, which consists of N bases, each of which can only be one of {A, C, T, G}.

I'd like to formalize the constraints with an enum, but I'm wondering what the most idiomatic way of emulating an enum is in Go.

Enums Solutions


Solution 1 - Enums

Quoting from the language specs:Iota

> Within a constant declaration, the predeclared identifier iota represents successive untyped integer constants. It is reset to 0 whenever the reserved word const appears in the source and increments after each ConstSpec. It can be used to construct a set of related constants:

const (  // iota is reset to 0
        c0 = iota  // c0 == 0
        c1 = iota  // c1 == 1
        c2 = iota  // c2 == 2
)

const (
        a = 1 << iota  // a == 1 (iota has been reset)
        b = 1 << iota  // b == 2
        c = 1 << iota  // c == 4
)

const (
        u         = iota * 42  // u == 0     (untyped integer constant)
        v float64 = iota * 42  // v == 42.0  (float64 constant)
        w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

> Within an ExpressionList, the value of each iota is the same because it is only incremented after each ConstSpec:

const (
        bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
        bit1, mask1                           // bit1 == 2, mask1 == 1
        _, _                                  // skips iota == 2
        bit3, mask3                           // bit3 == 8, mask3 == 7
)

> This last example exploits the implicit repetition of the last non-empty expression list.


So your code might be like

const (
        A = iota
        C
        T
        G
)

or

type Base int

const (
        A Base = iota
        C
        T
        G
)

if you want bases to be a separate type from int.

Solution 2 - Enums

Referring to the answer of jnml, you could prevent new instances of Base type by not exporting the Base type at all (i.e. write it lowercase). If needed, you may make an exportable interface that has a method that returns a base type. This interface could be used in functions from the outside that deal with Bases, i.e.

package a

type base int

const (
    A base = iota
    C
    T
    G
)


type Baser interface {
    Base() base
}

// every base must fulfill the Baser interface
func(b base) Base() base {
    return b
}


func(b base) OtherMethod()  {
}

package main

import "a"

// func from the outside that handles a.base via a.Baser
// since a.base is not exported, only exported bases that are created within package a may be used, like a.A, a.C, a.T. and a.G
func HandleBasers(b a.Baser) {
    base := b.Base()
    base.OtherMethod()
}


// func from the outside that returns a.A or a.C, depending of condition
func AorC(condition bool) a.Baser {
    if condition {
       return a.A
    }
    return a.C
}

Inside the main package a.Baser is effectively like an enum now. Only inside the a package you may define new instances.

Solution 3 - Enums

You can make it so:

type MessageType int32

const (
	TEXT   MessageType = 0
	BINARY MessageType = 1
)

With this code compiler should check type of enum

Solution 4 - Enums

It's true that the above examples of using const and iota are the most idiomatic ways of representing primitive enums in Go. But what if you're looking for a way to create a more fully-featured enum similar to the type you'd see in another language like Java or Python?

A very simple way to create an object that starts to look and feel like a string enum in Python would be:

package main

import (
	"fmt"
)

var Colors = newColorRegistry()

func newColorRegistry() *colorRegistry {
	return &colorRegistry{
		Red:   "red",
		Green: "green",
		Blue:  "blue",
	}
}

type colorRegistry struct {
	Red   string
	Green string
	Blue  string
}

func main() {
	fmt.Println(Colors.Red)
}

Suppose you also wanted some utility methods, like Colors.List(), and Colors.Parse("red"). And your colors were more complex and needed to be a struct. Then you might do something a bit like this:

package main

import (
	"errors"
	"fmt"
)

var Colors = newColorRegistry()

type Color struct {
	StringRepresentation string
	Hex                  string
}

func (c *Color) String() string {
	return c.StringRepresentation
}

func newColorRegistry() *colorRegistry {

	red := &Color{"red", "F00"}
	green := &Color{"green", "0F0"}
	blue := &Color{"blue", "00F"}

	return &colorRegistry{
		Red:    red,
		Green:  green,
		Blue:   blue,
		colors: []*Color{red, green, blue},
	}
}

type colorRegistry struct {
	Red   *Color
	Green *Color
	Blue  *Color

	colors []*Color
}

func (c *colorRegistry) List() []*Color {
	return c.colors
}

func (c *colorRegistry) Parse(s string) (*Color, error) {
	for _, color := range c.List() {
		if color.String() == s {
			return color, nil
		}
	}
	return nil, errors.New("couldn't find it")
}

func main() {
	fmt.Printf("%s\n", Colors.List())
}

At that point, sure it works, but you might not like how you have to repetitively define colors. If at this point you'd like to eliminate that, you could use tags on your struct and do some fancy reflecting to set it up, but hopefully this is enough to cover most people.

Solution 5 - Enums

There is a way with struct namespace.

The benefit is all enum variables are under a specific namespace to avoid pollution. The issue is that we could only use var not const

type OrderStatusType string

var OrderStatus = struct {
	APPROVED         OrderStatusType
	APPROVAL_PENDING OrderStatusType
	REJECTED         OrderStatusType
	REVISION_PENDING OrderStatusType
}{
	APPROVED:         "approved",
	APPROVAL_PENDING: "approval pending",
	REJECTED:         "rejected",
	REVISION_PENDING: "revision pending",
}

Solution 6 - Enums

As of Go 1.4, the go generate tool has been introduced together with the stringer command that makes your enum easily debuggable and printable.

Solution 7 - Enums

I am sure we have a lot of good answers here. But, I just thought of adding the way I have used enumerated types

package main

import "fmt"

type Enum interface {
	name() string
	ordinal() int
	values() *[]string
}

type GenderType uint

const (
	MALE = iota
	FEMALE
)

var genderTypeStrings = []string{
	"MALE",
	"FEMALE",
}

func (gt GenderType) name() string {
	return genderTypeStrings[gt]
}

func (gt GenderType) ordinal() int {
	return int(gt)
}

func (gt GenderType) values() *[]string {
	return &genderTypeStrings
}

func main() {
	var ds GenderType = MALE
	fmt.Printf("The Gender is %s\n", ds.name())
}

This is by far one of the idiomatic ways we could create Enumerated types and use in Go.

Edit:

Adding another way of using constants to enumerate

package main

import (
	"fmt"
)

const (
	// UNSPECIFIED logs nothing
	UNSPECIFIED Level = iota // 0 :
	// TRACE logs everything
	TRACE // 1
	// INFO logs Info, Warnings and Errors
	INFO // 2
	// WARNING logs Warning and Errors
	WARNING // 3
	// ERROR just logs Errors
	ERROR // 4
)

// Level holds the log level.
type Level int

func SetLogLevel(level Level) {
	switch level {
	case TRACE:
		fmt.Println("trace")
		return

	case INFO:
		fmt.Println("info")
		return

	case WARNING:
		fmt.Println("warning")
		return
	case ERROR:
		fmt.Println("error")
		return

	default:
		fmt.Println("default")
		return

	}
}

func main() {

	SetLogLevel(INFO)

}

Solution 8 - Enums

For a use case like this, it may be useful to use a string constant so it can be marshaled into a JSON string. In the following example, []Base{A,C,G,T} would get marshaled to ["adenine","cytosine","guanine","thymine"].

type Base string

const (
	A Base = "adenine"
	C      = "cytosine"
	G      = "guanine"
	T      = "thymine"
)

When using iota, the values get marshaled into integers. In the following example, []Base{A,C,G,T} would get marshaled to [0,1,2,3].

type Base int

const (
    A Base = iota
    C
    G
    T
)

Here's an example comparing both approaches:

https://play.golang.org/p/VvkcWvv-Tvj

Solution 9 - Enums

Here is an example that will prove useful when there are many enumerations. It uses structures in Golang, and draws upon Object Oriented Principles to tie them all together in a neat little bundle. None of the underlying code will change when a new enumeration is added or deleted. The process is:

  • Define an enumeration structure for enumeration items: EnumItem. It has an integer and string type.
  • Define the enumeration as a list of enumeration items: Enum
  • Build methods for the enumeration. A few have been included:
    • enum.Name(index int): returns the name for the given index.
    • enum.Index(name string): returns the name for the given index.
    • enum.Last(): returns the index and name of the last enumeration
  • Add your enumeration definitions.

Here is some code:

type EnumItem struct {
    index int
    name  string
}

type Enum struct {
    items []EnumItem
}

func (enum Enum) Name(findIndex int) string {
    for _, item := range enum.items {
        if item.index == findIndex {
            return item.name
        }
    }
    return "ID not found"
}

func (enum Enum) Index(findName string) int {
    for idx, item := range enum.items {
        if findName == item.name {
            return idx
        }
    }
    return -1
}

func (enum Enum) Last() (int, string) {
    n := len(enum.items)
    return n - 1, enum.items[n-1].name
}

var AgentTypes = Enum{[]EnumItem{{0, "StaffMember"}, {1, "Organization"}, {1, "Automated"}}}
var AccountTypes = Enum{[]EnumItem{{0, "Basic"}, {1, "Advanced"}}}
var FlagTypes = Enum{[]EnumItem{{0, "Custom"}, {1, "System"}}}

Solution 10 - Enums

Refactored https://stackoverflow.com/a/17989915/863651 to make it a bit more readable:

package SampleEnum

type EFoo int

const (
	A EFoo = iota
	C
	T
	G
)

type IEFoo interface {
	Get() EFoo
}

func(e EFoo) Get() EFoo { // every EFoo must fulfill the IEFoo interface
	return e
}

func(e EFoo) otherMethod()  { // "private"
	//some logic
}

Solution 11 - Enums

This is a safe way to implement enum in golang:

package main

import (
	"fmt"
)

const (
	MALE   = _gender(1)
	FEMALE = _gender(2)
	RED    = _color("RED")
	GREEN  = _color("GREEN")
	BLUE   = _color("BLUE")
)

type Gender interface {
	_isGender()
	Value() int
}

type _gender int

func (_gender) _isGender() {}

func (_g _gender) Value() int {
	return int(_g)
}

type Color interface {
	_isColor()
	Value() string
}

type _color string

func (_color) _isColor() {}

func (_c _color) Value() string {
	return string(_c)
}

func main() {
	genders := []Gender{MALE, FEMALE}
	colors := []Color{RED, GREEN, BLUE}
	fmt.Println("Colors =", colors)
	fmt.Println("Genders =", genders)
}

The output:

Colors = [RED GREEN BLUE]
Genders = [1 2]

Solution 12 - Enums

Also, this is a pretty effective way to store different roles in one location in a byte, where the first value is set to 1, bit shifted by an iota.

package main

import "fmt"

const (
	isCaptain = 1 << iota
	isTrooper
	isMedic

	canFlyMars
	canFlyJupiter
	canFlyMoon
)

func main() {
	var roles byte = isCaptain | isMedic | canFlyJupiter
	//Prints a binary representation.
	fmt.Printf("%b\n", roles)
	fmt.Printf("%b\n", isCaptain)
	fmt.Printf("%b\n", isTrooper)
	fmt.Printf("%b\n", isMedic)

	fmt.Printf("Is Captain? %v\n", isCaptain&roles == isCaptain)
	fmt.Printf("Is Trooper? %v", isTrooper&roles == isTrooper)

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncarbocationView Question on Stackoverflow
Solution 1 - EnumszzzzView Answer on Stackoverflow
Solution 2 - EnumsmetakeuleView Answer on Stackoverflow
Solution 3 - EnumsAzatView Answer on Stackoverflow
Solution 4 - EnumsBecca PetrinView Answer on Stackoverflow
Solution 5 - EnumsYu HuangView Answer on Stackoverflow
Solution 6 - EnumsZippoView Answer on Stackoverflow
Solution 7 - EnumswandermonkView Answer on Stackoverflow
Solution 8 - EnumsGrokifyView Answer on Stackoverflow
Solution 9 - Enumsuser3842449View Answer on Stackoverflow
Solution 10 - EnumsXDSView Answer on Stackoverflow
Solution 11 - EnumsBaiJiFeiLongView Answer on Stackoverflow
Solution 12 - EnumsmonkrusView Answer on Stackoverflow