ChatGPT解决这个技术问题 Extra ChatGPT

What is an idiomatic way of representing enums in Go?

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.

In go standard packages they're represented as constants. See golang.org/pkg/os/#pkg-constants
@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.
Now that 1.18 is upon us, I wonder if generics have opened up any fresh opportunities for conceptualizing enums in go.

z
zzzz

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.


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.
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
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).
You can use iota + 1 to not begin at 0.
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.
T
Taavi

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.


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.
@metakeule I'm trying to understand your example but your choice in variable names has made it exceedingly difficult.
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!
A
Azat

You can make it so:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

With this code compiler should check type of enum


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.
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?
@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
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.
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.
B
Becca Petrin

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.


Any way to use this with a switch statement, like in Java?
Actually, I got it working using the public Colors in your example, e.g. case Colors.Red: ...
Y
Yu Huang

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",
}

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.
How can I use that as an enum?
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.
Z
Zippo

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.


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.
w
wandermonk

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)

}

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.
G
Grokify

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


a
adiga

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

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"}}}

X
XDS

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
}

B
BaiJiFeiLong

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]

m
monkrus

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)

}

k
kaushik

I created the enum this way. Suppose we need an enum representing gender. Possible values are Male, Female, Others

package gender

import (
    "fmt"
    "strings"
)

type Gender struct {
    g string
}

var (
    Unknown = Gender{}
    Male    = Gender{g: "male"}
    Female  = Gender{g: "female"}
    Other   = Gender{g: "other"}
)

var genders = []Gender{
    Unknown,
    Male,
    Female,
    Other,
}

func Parse(code string) (parsed Gender, err error) {
    for _, g := range genders {
        if g.g == strings.ToLower(code) {
            if g == Unknown {
                err = fmt.Errorf("unknown gender")
            }
            parsed = g
            return
        }
    }

    parsed = Unknown
    err = fmt.Errorf("unknown gender", code)
    return
}

func (g Gender) Gender() string {
    return g.g
}