Why no enums?

I’d love to be able to write a function that only accepts a subset of string values. Other languages do this really simply with enum types. Why doesn’t Go?

Thanks so much for all the helpful answers :) I don’t totally understand why I’m being downvoted. Please shed some light there.

Posted Nov 10, 2022, 8:27 AM UTC · edited

100113
Necessary-Cow-204•

Although greatly discussed, your question is very much valid. A community that strives for best should always be open to raise questions and answering them.

Many Gopers tend to have a bit of a sports fan mentality, in which everything that might be perceived as criticism is automatically downvoted by some.

Ironically, although the core Go team is strict when it comes to excepting proposals, they're fairly open minded in giving room for feedback and raising questions.

Honestly this side of the community is not one of Go's strengths.

To your initial question - you can look up enum proposals in Go's issues page on GitHub. There has been more than one to my recollection, and you should be able to find a thorough discussion there.

59
army007•

Most of the blind go fan I found, either they haven't worked with other languages significantly or less experienced. Given go is a relatively new language there are lots of such go user. Experienced gophers who have used other languages are are aware of its limitations and admit it easily. They don't take it personally.

8
pi_sqaure•

Many Gopers tend to have a bit of a sports fan mentality

This was the point when I upvoted this posting.

4
CountyExoticOP•

Appreciate the thoughtful answer :)

1
Evening_Hunter•

I have spotted the same things as well. Though I believe it is more related with this sub but not Gophers community. I do not mean this sub is poisoned but I want to avoid judging whole community by looking through one window only.

1
TommyDJones•

Since I tried Rust, I am dying to get proper enums in Go :/

29
jug6ernaut•

Once you use Rust enums everything else just feels lacking.

10
bhechinger•

I can't think of any other language with enums even close to Rust enums. They're freaking amazing.

3
jug6ernaut•

Kotlin's Sealed Classes come close, they are basically sum types. The main thing Kotlin is missing on this front is pattern matching.

3
bhechinger•

I've never used a language that did pattern matching before. I'm *really* liking it a lot.

1
fredoverflow•

I can't think of any other language with enums even close to Rust enums.

Scala? https://dotty.epfl.ch/docs/reference/enums/enums.html

2
bhechinger•

Can members of an enum in Scala be disparate types? If so I didn't know that and I'm now kicking myself for not learning it all those years ago when I had the chance.

2
AWDDude•

I too would like enums. Iota is horrible.

26
dotaleaker•

Unpopular opinion in this sub, but ppl here will assure you Go doesn't need Enums. Imo the way currently enums are handled by iota is a lot of boilerplate code, and enum would probably solve it.

23
0xjnml•

Go's iota has no relationship with enum types, which Go does not have. So iota does not "handle" nor emulate enums in the first place.

Iota in Go serves a different purpose and serves that purpose well.

6
Kindred87•

Writing this to address those that hold the perspective that enums aren't implemented for ideological or "just cuz" reasons. Do not mistake this for saying "no enums is fine".

In my experience of these discussions cropping up repeatedly over the years, Go missing certain features has been the result of an intersection (or lack thereof) between implementation complexity, performance tradeoff, proven use cases, fit with existing features, cohesiveness with Go's design pillars, and agreement on syntax.

It is exceptionally challenging to reconcile all of these metrics simultaneously, particularly as more features are added over time. Wanting a feature or a specific flavor of a feature isn't enough to get it added to the spec.

Edit: Thank you Rob, for the gold.

20
SolaireDeSun•

You give go devs a bit too much credit here - they have a vast corpus of historical approaches to draw from they are not developing in a vacuum. Enums are not a wildly complex feature with syntax variations that are vast and differentiable.

It’s just architect astronauts trying to go to mars before they build their first bike

9
Kindred87•

Talking about my personal experience isn't giving anyone credit.

Zooming out, I'm curious why you use a language that you feel is designed and implemented by incompetent developers. Wouldn't you prefer to use a language led by astronauts that built the bike first, as you put it?

-3
SolaireDeSun•

The language I use is informed by more than just my desires sadly. Snark aside, I’m not disparaging Go the language as much as I am commenting on how far people are willing to go to make excuses for Go devs. Poor communication and slow to design obvious solutions is frustrating.

They spend time designing over engineered SAT solvers instead of making go more pleasant

2
amlunita•

Go is pleasant in many forms... but I understand the ways where it is not

0

Right - and in every case they end up implementing the feature anyway, in the way that we all thought it should work the whole time. Case in point: generics.

6
Kindred87•

Generics is a perfect example of what I described. It required literal years of design and debate before being ready for implementation. The initial implementation stage then took north of 10K developer hours (according to one of the blog posts).

These things are as I said, extremely challenging.

0†

Language design is extremely challenging in general, totally agree with you there. But generics specifically is not. Everyone said excluding generics was a mistake at the beginning of the language. Generics exist in many other contemporary languages. It's a solved problem. One that Go rejected for years, for no reason, and provided no novelty with the eventual solution that they came up with.

2
amlunita•

10K ? Fetch! And here you see me complaining for waste four fetching hours in fetching design

2
drvd•

Nobody can agree on what exactly "enums" should be and how they should behave.

16
drmariopepper•

I would like full-on algebraic types, but that will never happen in go

12
drvd•

Won't happen in Rust either.

2
SomeWeirdFruit•

People downvotes when they dont like the question. Which is pretty stupid ngl

16
CountyExoticOP•

sorry if I questioned language design but as a newly converted gopher this just really really felt like it was missing… coming from C++, java, rust, and python.

2
SomeWeirdFruit•

You don't need to sorry. The people who downvote you have loser mentality i think. For me when i don't like / don't know the question i just skip. If i know the answer i will help. I rarely ever downvote anything (unless i got in a heat argument lol)

3
CountyExoticOP•

I feel you :). I basically only downvote when it’s flat out wrong or misinformation.

1
bozdoz•

I desire this too and found this solution from a stackoverflow answer:

https://gist.github.com/bozdoz/df442b61cf1224be59ae4f03ecaff662

Idiomatic or not, it works

14
bitcycle•

IMHO, that interface is unnecessary.

1
bozdoz•

unless you want to restrict the use_dessert function to only allow a dessert type and not an int type; in that case, the interface is what makes this work

2
bitcycle•

Yeah, if I wanted to disallow that behavior I would probably use type struct instead of type int.

package seasons

import "fmt"

type season struct {
	name string
}

var (
	Summer = season{name: "Summer"}
	Autumn = season{name: "Autumn"}
	Winter = season{name: "Winter"}
	Spring = season{name: "Spring"}
)

func (s season) String() string {
	return s.name
}

func Parse(s string) (season, error) {
	allowed := map[string]season{
		Summer.name: Summer, 
		Autumn.name: Autumn, 
		Winter.name: Winter, 
		Spring.name: Spring, 
        }
        if v, ok := allowed[s]; ok {
                return v, nil
        }
        return nil, fmt.Errorf("invalid season: %s", s)
}
package main

import "fmt"

func main() {
	fmt.Println(seasons.Summer)
}
2
jetexex•

And how i can put seasons.Summer into variable in clients code? and pass it into function? or maybe store it in config structure?

1
Potatoes_Fall•

interface here isn't doing much, it's just the type system. check out github.com/alvaroloes/enumer for something more enumer like. If you don't like generated code like me, it's easy to write your own unmarshal / marshal methods

1
bozdoz•

The interface is what makes it restrictive. Otherwise the function could take any int.

2
Potatoes_Fall•

who is calling functions with integer literals???

1
masklinn•

The Go team looked at C enums and saw that they were useless, which is true.

Rather than implement useful enums e.g. type-safe, or even sum types, they removed enums.

Assuming the idea was even known and entertained, one of the sticking points was likely the obsession with zero values: if you have a type-safe enum with custom or string-valued discriminants all the values might be non-zero.

14
mee8Ti6Eit•

How do you make enums work with reflection? Either you kneecap reflection or you kneecap type safety.

Anyway once you introduce IPC which is a significant use case for enums you have to deal with invalid values anyway. Enums just don't add that much to the language, ask anyone who has used Go for a long time with an open mind.

1
masklinn•

How do you make enums work with reflection?

The same way you do with booleans or integers.

Anyway once you introduce IPC which is a significant use case for enums you have to deal with invalid values anyway.

Like every other type, yes.

Enums just don't add that much to the language, ask anyone who has used Go for a long time with an open mind.

Ah yes, ask the people who have basically no clue, makes complete sense.

3
mee8Ti6Eit•

There are no invalid values for every other type.

1
masklinn•

Of course there are: receive a value of entirely the wrong type and it's going to be an invalid value, even for the most permissive of types.

Not that it would matter if only some types had invalid values (like every integer type, or invalid escapes in strings depending on format), as that would still mean the concept of an invalid value is necessary.

5
Gentleman-Tech•

I'm with most people here. They'd be useful.

I use a switch statement to validate values. It works well but it would be better to have this in the type system

13
salman0149•

Try iota

8†
CountyExoticOP•

I have but it has limitations

2
[deleted]•

[deleted]

1
type Enum[T any] interface {
	String() string
	Value() T
}

type PrivateFolder interface {
	Enum[string]
	hidden()
}

type privateFolder string

func (pf privateFolder) hidden() {}

func (pf privateFolder) String() string {
	return pf.Value()
}

func (pf privateFolder) Value() string {
	return string(pf)
}

const (
	InboxFolder privateFolder = "Inbox"
	SentFolder  privateFolder = "Sent"
)

func (f *Folders) GetPrivateFolder(folder PrivateFolder) {}
0
[deleted]•

[deleted]

0
[deleted]•

[deleted]

1
dead_alchemy•

https://www.reddit.com/r/golang/comments/uvpygm/comment/i9pfrdc/

That Reddit post seems to summarize the topic. I don’t fully understand the design trade offs, but I hope it helps answer your question.

You can get at what you want using the type system though, so you aren’t completely without recourse.

8
masklinn•

I don’t think this is correct, it talks about variant types which are a way of combining types: I would assume they refer to something like generic type unions.

Though since that is now supported for generics it could gain runtime / compiler support and serve as your variant type, if that’s what you wanted to implement.

1
bitcycle•

I've worked in a few different languages and Go doesn't seem all that unique in the way that it doesn't provide an ENUM primitive. However, I looked around a bit and found go-enum which seems pretty neat. Also, I have no problem writing a custom string or int/iota type. In some cases you want to be able to serialize/deserialize with them -- and in others you don't need that.

8

I wish we had real enums. It’s hard to standardize iota enum implementation across an entire org. I use code gen https://github.com/abice/go-enum at my company to help reduce all the boiler plate / standardize implementation, not perfect but it makes it easier

7
chinaisasshole•

This is why protobuf is a good companion with Go. Allows you to specify your data spec outside of go specific scope

7
ZalgoNoise•

Yeah but for the amount of LOC you're importing you better have a good reason or just as a personal project

1
violently_yarrow•

Provided I'm definitely a Golang novice, and sincerely interested: what motivates the position that importing sizable LOC requires justification?

Are there downsides to LOC imported at compile time, or is the preference against LOC more ideological?

1
ZalgoNoise•

Strictly speaking of responsibility, all libraries you import in a major / commercial project are your responsibility as a software engineer.

This means that you (should) read the library you're importing, forking / contributing as needed and most importantly respond on your customers issues where that library interferes.

If you blindly import and hope for the best then something backfires, you're not just going to open an issue on the original library's repo (implying the issue is there). You will need to fix it yourself as well for the sake of your product and your (paying) customers.

Importing a library is a very sensitive action and that is why the LOC matter. This is another reason why developers are very fond of the standard library.

But in my personal projects, I use protobuf and gRPC too

2
Rataridicta•

Typed strings or ints is the way to do it in Go :)

6
CountyExoticOP•

AFAIK this doesn’t enforce it at compile time, right?

6
jerf•

There's a variety of ways to get it to partially enforce this or that, but there's no single way I know to get all the desirable properties of enumerations at once in Go.

Probably most relevant here is that you can enforce that only valid values or zero values exist:

type MyEnum struct {
    val int // or whatever type you like
}

var ValueOne = MyEnum{1}
var ValueTwo = MyEnum{2}
var ValueInvalid = MyEnum{0}

External packages will only be able to spontaneously create ValueInvalid. Any other value must have come from your variables, and no other values can be created. If you have a viable zero value you can set your enumeration to that and solve that problem too, though I often like to leave the zero value as always invalid even if I have a zero value otherwise, depending on my circumstances.

There's other options, depending on exactly what you want.

3
ZalgoNoise•

There are more idiomatic ways of expressing enums in Go:

type Cat int

const (
  Undefined Cat = iota
  Domestic
  Feral
  Wild
)
7
jerf•

That is one of the options. It has some disadvantages, such as being very easy to create invalid values. But it has the advantage of simplicity.

There just isn't one technique that dominates unfortunately.

5
BubblyKombucha•

Inspired by this, one minor annoyance I have with Go enums is that (if you use integer iota values), the "name" is lost if you wanted to go the other direction; e.g. a function accepts one of your enum types and wants to log what the name of it is, and not just its integer value. Working off the code you wrote, a possibly elegant solution to get the reverse name mapping to work and still prevent the caller from creating invalid values could be like:

type MyEnum struct {
    val string
}

// implement the Stringer
func (e MyEnum) String() string {
    return e.val
}

var ValueOne = MyEnum{"ValueOne"}
var ValueTwo = MyEnum{"ValueTwo"}
var ValueInvalid = MyEnum{"ValueInvalid"}

// how to log the enum sent
func PrintEnumName(v MyEnum) {
    fmt.Printf("You gave me a %s\n", v)
}

Then you and outside callers who use your enums can print its name; but outside callers can't create a custom/invalid MyEnum since they can't set the private val string.

You could do this with integer values too by defining a slice of all the names, if you were concerned about data size or really needed your enum to be integers:

type MyEnum struct {
    val int
}

var names = []string{"ValueInvalid", "ValueOne", "ValueTwo"}

func (e MyEnum) String() {
    // "guaranteed" in bounds since only you can
    // create valid enums in your source package
    return names[e.val]
}
1
Evening_Hunter•

You can use `stringer` tool to generate names for `iota` constants: https://pkg.go.dev/golang.org/x/tools/cmd/stringer

3
ZalgoNoise•

You can use two map converters as well, such as map[T]string and map[string]T where your stringer impl calls the appropriate map entry, and a converter from string to T for your factories.

Go-enum does this with enums concatenated as string and split with certain index values.

3
Rataridicta•

Just make the type private, and the constants public. That will enforce it :)

i.e.

type myEnum string

const (
  Enum1 myEnum = "something"
  Enum2 myEnum = "somethingElse"
)

or

type myEnum int

const (
  Enum1 myEnum = iota + 1
  Enum2
)
func (e myEnum) ToString() { ... }
2
Rami3L_Li•

IIRC, an enum in C is also a newtype around the integer type which is not type safe either (you are responsible for creating invalid values like in Golang), just less explicit… So Golang without enums doesn’t lose any type safety compared to C, actually. Of course I’d like a Rust enum but it’s another beast of its own.

1
OnlyOneDottedLine•

I highly recommend asking this in https://groups.google.com/g/golang-nuts

That's where developers more closely engaged with language development tend to gather.

5

What you seek is a feature of sum types, not a feature of enums.

Why doesn't Go have sum types? Probably because Oberon-2 doesn't have sum types.

Why doesn't Oberon-2 have sum types? Probably because it's old enough that they were still a novel concept.

4
servermeta_net•

Oberon-2

what has oberon-2 to do with golang? I googled it but coudln't find a reference.

7
betelgeuse_7•

Oberon is one of the languages Go was inspired by, but I don't see what adding enums to Go would have anything to do with one of the many languages Go was inspired by.

Edit: Look at "Origins of Go" chapter in "The Go Programming Language" book by Alan Donovan, and Brian Kernighan, for reference.

7

I misspoke. I meant Limbo, which Go is basically a clone of.

Although Go differs slightly from Limbo where it takes from Oberon-2.

1
kstacey•

You can sort of do it with typed strings and constants, but it's not the same.

3
LasagneEnthusiast•

Simply create a custom string type and define your values as constants of that type.

Edit: Also, not many languages offer string enums, so Go is not special in that regard.

2†
InternetAnima•

The problem is that using invalid values is not a compile error. That takes a lot of the enum safety away.

14
CountyExoticOP•

This. I want compile time checking.

10