What is an idiomatic way of representing enums in Go?

441,338

Solution 1

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

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

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

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

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",
}
Share:
441,338
carbocation
Author by

carbocation

Updated on July 08, 2022

Comments

  • carbocation
    carbocation almost 2 years

    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.

    • Denys Séguret
      Denys Séguret over 11 years
      In go standard packages they're represented as constants. See golang.org/pkg/os/#pkg-constants
    • lbonn
      lbonn over 11 years
    • carbocation
      carbocation almost 6 years
      @icza This question was asked 3 years before that. This can't be a duplicate of that one, assuming the arrow of time is in working order.
    • Jörg W Mittag
      Jörg W Mittag over 2 years
      @carbocation: That's not how duplicates work on Stack Overflow. Questions which are duplicates should be closed as a duplicate of the question with the best content, not the earliest one.
    • carbocation
      carbocation about 2 years
      Now that 1.18 is upon us, I wonder if generics have opened up any fresh opportunities for conceptualizing enums in go.
  • mna
    mna over 11 years
    great examples (I did not recall the exact iota behaviour - when it is incremented - from the spec). Personally I like to give a type to an enum, so it can be type-checked when used as argument, field, etc.
  • Deleplace
    Deleplace over 11 years
    Very interesting @jnml . But I'm kind of disappointed that static type-checking seems to be loose, for example nothing prevents me from using Base n°42 which never existed : play.golang.org/p/oH7eiXBxhR
  • mna
    mna over 11 years
    To complement on jnml, even semantically, nothing in the language says that the consts defined as Base represent the whole range of valid Base, it just says that these particular consts are of type Base. More constants could be defined elsewhere as Base too, and it is not even mutually exclusive (e.g. const Z Base = 0 could be defined and would be valid).
  • Niriel
    Niriel over 10 years
    Your method seems perfect for the cases where base is used only as method receiver. If your a package were to expose a function taking a parameter of type base, then it would become dangerous. Indeed, the user could just call it with the literal value 42, which the function would accept as base since it can be casted to an int. To prevent this, make base a struct: type base struct{value:int}. Problem: you cannot declare bases as constants anymore, only module variables. But 42 will never be cast to a base of that type.
  • Marçal Juan
    Marçal Juan almost 10 years
    You can use iota + 1 to not begin at 0.
  • fIwJlxSzApHEZIl
    fIwJlxSzApHEZIl over 7 years
    @metakeule I'm trying to understand your example but your choice in variable names has made it exceedingly difficult.
  • 425nesp
    425nesp over 6 years
    Constants are usually written in normal camelcase, not all uppercase. The initial uppercase letter means that variable is exported, which may or may not be what you want.
  • Jeremy Gailor
    Jeremy Gailor over 6 years
    I've noticed in the Go source code there is a mixture where sometimes constants are all uppercase and sometimes they are camelcase. Do you have a reference to a spec?
  • waynethec
    waynethec over 6 years
    @JeremyGailor I think 425nesp is just noting that the normal preference is for developers to use them as unexported constants so use camelcase. If the developer determines that it should be exported then feel free to use all uppercase or capital case because there's no established preference. See Golang Code Review Recommendations and Effective Go Section on Constants
  • Rodolfo Carvalho
    Rodolfo Carvalho over 6 years
    There is a preference. Just like variables, functions, types and others, constant names should be mixedCaps or MixedCaps, not ALLCAPS. Source: Go Code Review Comments.
  • S.R
    S.R over 5 years
    Do u know is oposite solution. I mean string -> MyType. Since one way solution is far from ideal. Here is sb gist that do what I want - but writing by hand is easy to make mistakes.
  • cbednarski
    cbednarski about 5 years
    You can declare constants with string values. IMO it is easier to do that if you intend to display them and don't actually need the numeric value.
  • Graham Nicholls
    Graham Nicholls over 4 years
    This is one of my bugbears in examples. FGS, I realise it's tempting, but don't name the variable the same as the type!
  • Kosta
    Kosta about 4 years
    Note that in your last example, C, T, G are untyped numeric consts, not type Base. For all consts to be Base, you need to repeat Base on each line.
  • Kosta
    Kosta about 4 years
    Note that e.g. functions expecting a MessageType will happily accept untyped numeric consts, e.g. 7. Furthermore, You can cast any int32 to MessageType. If you are aware of this, I think this is the most idiomatic way in go.
  • Deleplace
    Deleplace almost 3 years
    @Kosta in the current version with A base = iota, all the values correctly have the type Base. If they were untyped numeric consts, we would be able to use them as ints, but we can't play.golang.org/p/Dpop4S3qdNs
  • Grokify
    Grokify almost 3 years
    This is syntactically nice, but I'm paranoid about people changing constants. See this example: play.golang.org/p/9D1tMQJVmIc . If namespacing is important, I would lean towards placing them in their own package.
  • Bruno Negrão Zica
    Bruno Negrão Zica almost 3 years
    How can I use that as an enum?
  • ooouuiii
    ooouuiii almost 3 years
    Is iota really beneficial construct? Iota most of the time offers sparing like 2 characters per line (ok + whitespaces), what it cost You is: the enum values depends on the order - once it changes in the source (by e.g. CVS merge) You may corrupt existing persisted data by shifting the index, causing altering the meaning of the data. Second, if You have like 15 items in enum and just want quickly to check, what item corresponds to value 11, are You going to count the lines every time? Seriously, we write it once, read it dozens of times.
  • David Good
    David Good almost 3 years
    Any way to use this with a switch statement, like in Java?
  • David Good
    David Good almost 3 years
    Actually, I got it working using the public Colors in your example, e.g. case Colors.Red: ...
  • Gurleen Sethi
    Gurleen Sethi over 2 years
    I agree with @Grokify, although this looks syntactically pleasing, the use of var here is quite dangerous, as it is prone to change, which defeats the whole point of an enum.
  • Martin M.
    Martin M. over 2 years
    @RodolfoCarvalho interestingly enough go.dev/doc/effective_go#constants has constant names that are only upper case
  • Rodolfo Carvalho
    Rodolfo Carvalho over 2 years
    @MartinM. I'd call that an "accident" -- those constants (KB, MB, GB, etc) just happen to be abbreviations (see github.com/golang/go/wiki/CodeReviewComments#initialisms) to well-known units.