Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: add typed enum support #19814

Open
derekperkins opened this issue Mar 31, 2017 · 191 comments
Open

proposal: spec: add typed enum support #19814

derekperkins opened this issue Mar 31, 2017 · 191 comments
Labels
Go2 LanguageChange NeedsInvestigation Proposal
Milestone

Comments

@derekperkins
Copy link

@derekperkins derekperkins commented Mar 31, 2017

I'd like to propose that enum be added to Go as a special kind of type. The examples below are borrowed from the protobuf example.

Enums in Go today

type SearchRequest int
var (
	SearchRequestUNIVERSAL SearchRequest = 0 // UNIVERSAL
	SearchRequestWEB       SearchRequest = 1 // WEB
	SearchRequestIMAGES    SearchRequest = 2 // IMAGES
	SearchRequestLOCAL     SearchRequest = 3 // LOCAL
	SearchRequestNEWS      SearchRequest = 4 // NEWS
	SearchRequestPRODUCTS  SearchRequest = 5 // PRODUCTS
	SearchRequestVIDEO     SearchRequest = 6 // VIDEO
)

type SearchRequest string
var (
	SearchRequestUNIVERSAL SearchRequest = "UNIVERSAL"
	SearchRequestWEB       SearchRequest = "WEB"
	SearchRequestIMAGES    SearchRequest = "IMAGES"
	SearchRequestLOCAL     SearchRequest = "LOCAL"
	SearchRequestNEWS      SearchRequest = "NEWS"
	SearchRequestPRODUCTS  SearchRequest = "PRODUCTS"
	SearchRequestVIDEO     SearchRequest = "VIDEO"
)

// IsValid has to be called everywhere input happens, or you risk bad data - no guarantees
func (sr SearchRequest) IsValid() bool {
	switch sr {
		case SearchRequestUNIVERSAL, SearchRequestWEB...:
			return true
	}
	return false
}

How it might look with language support

enum SearchRequest int {
    0 // UNIVERSAL
    1 // WEB
    2 // IMAGES
    3 // LOCAL
    4 // NEWS
    5 // PRODUCTS
    6 // VIDEO
}

enum SearchRequest string {
    "UNIVERSAL"
    "WEB"
    "IMAGES"
    "LOCAL"
    "NEWS"
    "PRODUCTS"
    "VIDEO"
}

The pattern is common enough that I think it warrants special casing, and I believe that it makes code more readable. At the implementation layer, I would imagine that the majority of cases can be checked at compile time, some of which already happen today, while others are near impossible or require significant tradeoffs.

  • Safety for exported types: nothing prevents someone from doing SearchRequest(99) or SearchRequest("MOBILEAPP"). Current workarounds include making an unexported type with options, but that often makes the resulting code harder to use / document.
  • Runtime safety: Just like protobuf is going to check for validity while unmarshaling, this provides language wide validation, anytime that an enum is instantiated.
  • Tooling / Documentation: many packages today put valid options into field comments, but not everyone does it and there is no guarantee that the comments aren't outdated.

Things to Consider

  • Nil: by implementing enum on top of the type system, I don't believe this should require special casing. If someone wants nil to be valid, then the enum should be defined as a pointer.
  • Default value / runtime assignments: This is one of the tougher decisions to make. What if the Go default value isn't defined as a valid enum? Static analysis can mitigate some of this at compile time, but there would need to be a way to handle outside input.

I don't have any strong opinions on the syntax. I do believe this could be done well and would make a positive impact on the ecosystem.

@jimmyfrasche
Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 31, 2017

@derekparker there's a discussion for making a Go2 proposal in #19412

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Mar 31, 2017

I read through that earlier today, but that seemed more focused on valid types, where this is focused on valid type values. Maybe this is a subset of that proposal, but also is a less far-reaching change to the type system that could be put into Go today.

@jimmyfrasche
Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 31, 2017

enums are a special case of sum types where all the types are the same and there's a value associated to each by a method. More to type, surely, but same effect. Regardless, it would be one or the other, sum types cover more ground, and even sum types are unlikely. Nothing's happening until Go2 because of the Go1 compatibility agreement, in any case, since these proposals would, at the very least, require a new keyword, should any of them be accepted

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Mar 31, 2017

Fair enough, but neither of these proposals is breaking the compatibility agreement. There was an opinion expressed that sum types were "too big" to add to Go1. If that's the case, then this proposal is a valuable middle ground that could be a stepping stone to full sum types in Go2.

@jimmyfrasche
Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 31, 2017

They both require a new keyword which would break valid Go1 code using that as an identifier

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Apr 1, 2017

I think that could be worked around

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 1, 2017

A new language feature needs compelling use cases. All language features are useful, or nobody would propose them; the question is: are they useful enough to justify complicating the language and requiring everyone to learn the new concepts? What are the compelling use cases here? How will people use these? For example, would people expect to be able to iterate over the set of valid enum values, and if so how would they do that? Does this proposal do more than let you avoid adding default cases to some switches?

@gopherbot gopherbot added this to the Proposal milestone Apr 1, 2017
@md2perpe
Copy link

@md2perpe md2perpe commented Apr 1, 2017

Here's the idiomatic way of writing enumerations in current Go:

type SearchRequest int

const (
	Universal SearchRequest = iota
	Web
	Images
	Local
	News
	Products
	Video
)

This has the advantage that it's easy to create flags that can be OR:ed (using operator |):

type SearchRequest int

const (
	Universal SearchRequest = 1 << iota
	Web
	Images
	Local
	News
	Products
	Video
)

I can't see that introducing a keyword enum would make it much shorter.

@bep
Copy link
Contributor

@bep bep commented Apr 1, 2017

@md2perpe that isn't enums.

  1. They cannot be enumerated, iterated.
  2. They have no useful string representation.
  3. They have no identity:
package main

import (
	"fmt"
)

func main() {
	type SearchRequest int
	const (
		Universal SearchRequest = iota
		Web
	)

	const (
		Another SearchRequest = iota
		Foo
	)

	fmt.Println("Should be false: ", (Web == Foo))
        // Prints: "Should be false:  true"
}

I totally agree with @derekperkins that Go needs some enum as first class citizen. How that would look like, I'm not sure, but I suspect it could be done without breaking the Go 1 glass house.

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Apr 1, 2017

@md2perpe iota is a very limited way to approach enums, which works great for a limited set of circumstances.

  1. You need an int
  2. You only need to be consistent inside your package, not representing external state

As soon as you need to represent a string or another type, which is very common for external flags, iota doesn't work for you. If you want to match against a external/database representation, I wouldn't use iota, because then ordering in source code matters and reordering would cause data integrity issues.

This isn't just an convenience issue to make code shorter. This is a proposal that will allow for data integrity in a way that is not enforceable by the language today.

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Apr 1, 2017

@ianlancetaylor

For example, would people expect to be able to iterate over the set of valid enum values, and if so how would they do that?

I think that is a solid use case, as mentioned by @bep. I think the iteration would look like a standard Go loop, and I think they would loop in the order that they were defined.

for i, val := range SearchRequest {
...
}

@mixedCase
Copy link

@mixedCase mixedCase commented Apr 2, 2017

If Go were to add anything more than iota, at that point why not go for algebraic data types?

@derekperkins
Copy link
Author

@derekperkins derekperkins commented Apr 2, 2017

By extension of ordering according to the definition order, and following the example of protobuf, I think that the default value of the field would be the first defined field.

@egonelbre
Copy link
Contributor

@egonelbre egonelbre commented Apr 2, 2017

@bep Not as convenient, but you can get all these properties:

package main

var SearchRequests []SearchRequest
type SearchRequest struct{ name string }
func (req SearchRequest) String() string { return req.name }

func Request(name string) SearchRequest {
	req := SearchRequest{name}
	SearchRequests = append(SearchRequests, req)
	return req
}

var (
	Universal = Request("Universal")
	Web       = Request("Web")

	Another = Request("Another")
	Foo     = Request("Foo")
)

func main() {
	fmt.Println("Should be false: ", (Web == Foo))
	fmt.Println("Should be true: ", (Web == Web))
	for i, req := range SearchRequests {
		fmt.Println(i, req)
	}
}

@Merovius
Copy link

@Merovius Merovius commented Apr 2, 2017

I don't think compile-time checked enums are a good idea. I believe go pretty much has this right right now. My reasoning is

  • compile-time checked enums are neither backwards nor forwards compatible for the case of additions or removals. #18130 spends significant effort to move go towards enabling gradual code repair; enums would destroy that effort; any package that ever wants to change a set of enums, would automatically and forcibly break all their importers.
  • Contrary to what the original comment claims, protobuf (for that specific reason) don't actually check the validity of enum fields. proto2 specifies that an unknown value for an enum should be treated like an unknown field and proto3 even specifies, that the generated code must have a way to represent them with the encoded value (exactly like go does currently with fake-enums)
  • In the end, it doesn't actually add a lot. You can get stringification by using the stringer tool. You can get iteration, by adding a sentinel MaxValidFoo const (but see above caveat. You shouldn't even have the requirement). You just shouldn't have the two const-decls in the first place. Just integrate a tool into your CI that checks for that.
  • I don't believe other types than ints are actually necessary. The stringer tool should already cover converting to and from strings; in the end, the generated code would be equivalent to what a compiler would generate anyway (unless you seriously suggest that any comparison on "string-enums" would iterate the bytes…)

Overall, just a huge -1 for me. Not only doesn't it add anything; it actively hurts.

@vasiliicuhar
Copy link

@vasiliicuhar vasiliicuhar commented Apr 2, 2017

I think current enum implementation in Go is very straightforward and provides enough compilation time checks. I actually expect some kind of Rust enums with basic pattern matching, but it possibly breaks Go1 guaranties.

@jimmyfrasche
Copy link
Member

@jimmyfrasche jimmyfrasche commented Apr 2, 2017

Since enums are a special case of sum types and the common wisdom is that we should use interfaces to simulate sum types the answer is clearly https://play.golang.org/p/1BvOakvbj2

(if it's not clear: yes, that is a joke—in classic programmer fashion, I'm off by one).

In all seriousness, for the features discussed in this thread, some extra tooling would be useful.

Like the stringer tool, a "ranger" tool could generate the equivalent of the Iter func in the code I linked above.

Something could generate {Binary,Text}{Marshaler,Unmarshaler} implementations to make them easier to send over the wire.

I'm sure there are a lot of little things like this that would be quite useful on occasion.

There are some vetting/linter tools for exhaustiveness checking of sum types simulated with interfaces. No reason there couldn't be ones for iota enums that tell you when cases are missed or invalid untyped constants are used (maybe it should just report anything other than 0?).

There's certainly room for improvement on that front even without language changes.

@sprstnd
Copy link

@sprstnd sprstnd commented Apr 3, 2017

Enums would complement the already established type system. As the many examples in this issue have shown, the building blocks for enums is already present. Just as channels are high level abstractions build on more primitives types, enums should be built in the same manner. Humans are arrogant, clumsy, and forgetful, mechanisms like enums help human programmers make less programming errors.

@alercah
Copy link

@alercah alercah commented Apr 3, 2017

@bep I have to disagree with all three of your points. Go idiomatic enums strongly resemble C enums, which do not have any iteration of valid values, do not have any automatic conversion to strings, and do not have necessarily distinct identity.

Iteration is nice to have, but in most cases if you want iteration, it is fine to define constants for the first and last values. You can even do so in a way that does not require updating when you add new values, since iota will automatically make it one-past-the-end. The situation where language support would make a meaningful difference is when the values of the enum are non-contiguous.

Automatic conversion to string is only a small value: especially in this proposal, the string values need to be written to correspond to the int values, so there is little to be gained over explicitly writing an array of string values yourself. In an alternate proposal, it could be worth more, but there are downsides to forcing variable names to correspond to string representations as well.

Finally, distinct identity I'm not even sure is a useful feature at all. Enums are not sum types as in, say, Haskell. They are named numbers. Using enums as flag values, for instance, is common. For instance, you can have ReadWriteMode = ReadMode | WriteMode and this is a useful thing. It's quite possible to also have other values, for instance you might have DefaultMode = ReadMode. It's not like any method could stop someone from writing const DefaultMode = ReadMode in any case; what purpose does it serve to require it to happen in a separate declaration?

@bep
Copy link
Contributor

@bep bep commented Apr 3, 2017

@bep I have to disagree with all three of your points. Go idiomatic enums strongly resemble C enums, which do not have any iteration of valid values, do not have any automatic conversion to strings, and do not have necessarily distinct identity.

@alercah, please don't pull this idomatic Go into any discussion as a supposedly "winning argument"; Go doesn't have built-in Enums, so talking about some non-existing idoms, make little sense.

Go was built to be a better C/C++ or a less verbose Java, so comparing it to the latter would make more sense. And Java does have a built-in Enum type ("Java programming language enum types are much more powerful than their counterparts in other languages. "): https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

And, while you may disagree with the "much more powerful part", the Java Enum type does have all of the three features I mentioned.

I can appreciate the argument that Go is leaner, simpler etc., and that some compromise must be taken to keep it this way, and I have seen some hacky workarounds in this thread that kind of works, but a set of iota ints do not alone make an enum.

@vasiliicuhar
Copy link

@vasiliicuhar vasiliicuhar commented Apr 3, 2017

Enumerations and automatic string conversions are good candidates for the 'go generate' feature. We have some solutions already. Java enums are something in the middle of classic enums and sum types. So it is a bad language design in my opinion.
The thing about idiomatic Go is the key, and I don't see strong reasons to copy all the features from language X to language Y, just because someone is familiar with.

Java programming language enum types are much more powerful than their counterparts in other languages

That was true a decade ago. See modern zero-cost implementation of Option in Rust powered by sum types and pattern matching.

@bep
Copy link
Contributor

@bep bep commented Apr 3, 2017

The thing about idiomatic Go is the key, and I don't see strong reasons to copy all the features from language X to language Y, just because someone is familiar with.

Note that I don't disagree too much with the conclusions given here, but the use of _ idiomatic Go_ is putting Go up on som artsy pedestal. Most software programming is fairly boring and practical. And often you just need to populate a drop-down box with an enum ...

@vasiliicuhar
Copy link

@vasiliicuhar vasiliicuhar commented Apr 3, 2017

//go:generate enumerator Foo,Bar
Written once, available everywhere. Note that the example is abstract.

@Merovius
Copy link

@Merovius Merovius commented Apr 3, 2017

@bep I think you misread the original comment. "Go idiomatic enums" was supposed to refer to the current construction of using type Foo int + const-decl + iota, I believe, not to say "whatever you are proposing isn't idiomatic".

@rsc rsc added the Go2 label Apr 3, 2017
@derekperkins
Copy link
Author

@derekperkins derekperkins commented Apr 3, 2017

@rsc Regarding the Go2 label, that's counter to my reasoning for submitting this proposal. #19412 is a full sum types proposal, which is a more powerful superset than my simple enum proposal here, and I would rather see that in Go2. From my perspective, the likelihood of Go2 happening in the next 5 years is tiny, and I'd rather see something happen in a shorter timeframe.

If my proposal of a new reserved keyword enum is impossible for BC, there are still other ways to implement it, whether it be a full-on language integration or tooling built into go vet. Like I originally stated, I'm not particular on the syntax, but I strongly believe that it would be a valuable addition to Go today without adding a significant cognitive burden for new users.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 4, 2017

A new keyword is not possible before Go 2. It would be a clear violation of the Go 1 compatibility guarantee.

Personally, I am not yet seeing the compelling arguments for enum, or, for that matter, for sum types, even for Go 2. I'm not saying they can't happen. But one of the goals of the Go language is simplicity of the language. It's not enough for a language feature to be useful; all language features are useful--if they weren't useful, nobody would propose them. In order to add a feature to Go the feature has to have enough compelling use cases to make it worth complicating the language. The most compelling use cases are code that can not be written without the feature, at least now without great awkwardness.

@vladimirschuka
Copy link

@vladimirschuka vladimirschuka commented Nov 20, 2018

but in case

type Status1 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

type Status2 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

How is about Status1.Started == Status2.Started ?
about Marshaling ?

If I change a position?

type Status uint8 enum {
  Started  // Status.Started == 0
  InProcess
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

I agree with @Goodwine about immutable types.

@zoonman
Copy link

@zoonman zoonman commented Nov 20, 2018

Marshaling is an interesting question.
This all depends on how are we going to treat the underlying value. If we are going to use actual values, therefore Status1.Started would be equal to Status2.Started.
If we are going with symbolic interpretation those would be considered as different values.

Inserting something will case a change in values (exactly the same way as it goes with iota).
To avoid this developer has to specify values alongside with declarations.

type Status uint8 enum {
  Started  0
  InProcess 2
  Stopped 1
}

This is obvious thing.
If we want to avoid such issues we have to provide predictable compiler output based on lexical interpretation of the enum values. I assume the simplest way - building a hash table or sticking to symbolic names (strings) unless custom type casting is defined.

@anjmao
Copy link

@anjmao anjmao commented Dec 27, 2018

I like how Rust is implemented Enums.

Default with no type specified

enum IpAddr {
    V4,
    V6,
}

Custom type

enum IpAddr {
    V4(string),
    V6(string),
}

home := IpAddr.V4("127.0.0.1");
loopback := IpAddr.V6("::1");

Complex types

enum Message {
    Quit,
    Move { x: int32, y: int32 },
    Write(String),
    ChangeColor(int32, int32, int32),
}

For sure even having simple enums like in C# which are stored as integral types would be great.

@vp2177
Copy link

@vp2177 vp2177 commented Jan 9, 2019

The above go beyond enums, those are discriminated unions, which are indeed more powerful, especially with pattern matching, which could be a minor extension to switch, something like:

switch something.(type) {
case Quit:
        ...
case ChangeColor; r, g, b := something:
        ...
case Write: // Here `something` is known to be a string
        ...
// Ideally Go would warn here about the missing case for "Move"
}

@mier85
Copy link

@mier85 mier85 commented Jun 22, 2019

I don't need any compile time checks of enums, as that could be dangerous as mentioned

What I needed several time would have been to iterate over all constants of a given type:

  • either for validation ( if we are very sure we only want to accept this or to simply ignore unknown options )
  • or for a list of possible constants ( think of dropdowns ).

We could do the validation with iota and specifying the end of the list. However using iota for anything else than just inside the code, would be fairly dangerous because stuff will break by inserting a constant at the wrong line ( I know we need to be aware of where we put things in programming, but a bug like that is a whole lot harder to find than other things). Additionally we have no description of what the constant actually stands for when it's a number. That leads to the next point:

A nice extra would be to specify stringify names for it.

@x-strong
Copy link

@x-strong x-strong commented Nov 6, 2019

What's to stop someone from doing something like this:

NotADay := Day{"NotADay"}
getTask(NotADay)

The consumer of such a variable may or may not catch that with proper checking of expected values (assuming no poor fall through assumptions in switch statements, like anything that's not Saturday or Sunday is a weekday, for instance), but it wouldn't be until runtime. I think one would prefer this type of mistake to be caught at compile time, not runtime.

@L-oris So What about this:

package main
import "yet/it/is/not/a/good/practice/in/Go/enum/example/day"

func main()
{
  // var foo day.Day
  foo := day.Day{}
  bar(foo)
}

func bar(day day.Day)
{
  // xxxxxxxxxx
}

What we want is NOT RUNTIME SILENCE & Weird BUG cause by the [return "nothing to do"] but a compile-time / coding-time ERROR REPORTING!
UNDERSTAND?

@ermik
Copy link

@ermik ermik commented Nov 7, 2019

  1. enum is indeed new type, which is what type State string does, there is no idiomatic need to introduce a new keyword. Go isn't about saving space in your source code, it is about readability, clarity of purpose.

  2. Lack of type safety, confusing the new string- or int-based types for actual strings/ints is the key hurdle. All enum clauses are declared as const, which creates a set of known values compiler can check against.

  3. Stringer interface is the idiom for representing any type as human-readable text. Without customization, type ContextKey string enums this is the string value, and for iota-generated enums it's the integer, much like XHR ReadyState codes (0 - unsent, 4 - done) in JavaScript.

    Rather, the problem lies with the fallibility of custom func (k ContextKey) String() string implementation, which is usually done using a switch that must contain every known enum clause constant.

  4. In a language like Swift, there is a notion of an exhaustive switch. This is a good approach for both the type checking against a set of consts and building an idiomatic way to invoke that check. The String() function, being a common necessity, is a great case for implementation.

Proposal

package main

import (
	"context"
	"strconv"
	"fmt"
	"os"
)

// State is an enum of known system states.
type DeepThoughtState int

// One of known system states.
const (
	Unknown DeepThoughtState = iota
	Init
	Working
	Paused
	ShutDown
)

// String returns a human-readable description of the State.
//
// It switches over const State values and if called on
// variable of type State it will fall through to a default
// system representation of State as a string (string of integer
// will be just digits).
func (s DeepThoughtState) String() string {
	// NEW: Switch only over const values for State
	switch s.(const) {
	case Unknown:
		return fmt.Printf("%d - the state of the system is not yet known", Unknown)
	case Init:
		return fmt.Printf("%d - the system is initializing", Init)
	} // ERR: const switch must be exhaustive; add all cases or `default` clause

	// ERR: no return at the end of the function (switch is not exhaustive)
}

// RegisterState allows changing the state
func RegisterState(ctx context.Context, state string) (interface{}, error) {
	next, err := strconv.ParseInt(state, 10, 32)
	if err != nil {
		return nil, err
	}
	nextState := DeepThoughtState(next)

	fmt.Printf("RegisterState=%s\n", nextState) // naive logging

        // NEW: Check dynamically if variable is a known constant
	if st, ok := nextState.(const); ok {
		// TODO: Persist new state
		return st, nil
	} else {
		return nil, fmt.Errorf("unknown state %d, new state must be one of known integers", nextState)
	}
}

func main() {
	_, err := RegisterState(context.Background(), "42")
	if err != nil {
		fmt.Println("error", err)
		os.Exit(1)
	}
	os.Exit(0)
	return
}

P.S. Associated values in Swift enums are one of my favorite gimmicks. In Go there is no place for them. If you want to have a value next to your enum data — use a strongly typed struct wrapping the two.

@egonelbre
Copy link
Contributor

@egonelbre egonelbre commented Dec 6, 2019

Few months ago I wrote a proof-of-concept for a linter that checks that enumerated types are properly handled. https://github.com/loov/enumcheck

Currently it uses comments to mark things as enumerations:

type Letter byte // enumcheck

const (
	Alpha Letter = iota
	Beta
	Gamma
)

func Switch(x Letter) {
	switch x { // error: "missing cases Beta and Gamma"
	case Alpha:
		fmt.Println("alpha")
	case 4: // error: "implicit conversion of 4 to Letter"
		fmt.Println("beta")
	default: // error: "Letter shouldn't have a default case"
		fmt.Println("default")
	}
}

I got stuck in figuring out how to handle all the implicit conversions, but it works decently for basic cases.

Note, currently it's still a work-in-progress, so things may change. e.g. instead of comments it could use some stub package for annotating the types, but comments are good-enough at the moment.

@pofl
Copy link

@pofl pofl commented Oct 20, 2020

The current implementation of enums in Go1 is the weirdest most inobvious enum implementation in any language ever that I am aware of. Even C implements them more nicely. The iota thing looks like a hack. And what the heck does iota mean anyway? How am I supposed to memorize that keyword? Go is supposed to be easy to learn. But that is just qiurky.

@DeedleFake
Copy link

@DeedleFake DeedleFake commented Oct 20, 2020

@pofl:
While I agree that Go enums are pretty awkward, iota is actually just a normal English word:

iota
noun

  1. a very small quantity; jot; whit.
  2. the ninth letter of the Greek alphabet (I, ι).
  3. the vowel sound represented by this letter.

Presumably, they were going for definition one in terms of the use in the language.

On a side note in response to an older comment in here:
While I'd really like discriminated unions in Go, too, I feel like they should be separate from actual enums. With the way generics are currently going, you may actually get something very similar to discriminated unions via type lists in interfaces. See #41716.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Oct 20, 2020

The use of iota in Go is loosely based on its use in APL. Quoting https://en.wikipedia.org/wiki/Iota:

In some programming languages (e.g., A+, APL, C++[6], Go[7]), iota (either as the lowercase symbol ⍳ or the identifier iota) is used to represent and generate an array of consecutive integers. For example, in APL ⍳4 gives 1 2 3 4.

@phuclv90
Copy link

@phuclv90 phuclv90 commented Apr 29, 2021

The iota thing looks like a hack. And what the heck does iota mean anyway? How am I supposed to memorize that keyword?

@pofl Blame the APL

@coip
Copy link

@coip coip commented May 20, 2021

still reading the above, so chance of more-noise-than-signal here, but wanted to dump some thoughts ive had for a while...
to throw in a concrete reference to pick apart @ your leisure:
https://github.com/betty200744/ultimate-go/blob/master/Language_Specification/enum/enum.go

feature-functionality as a subset of an enums proposal:
id like some means of validating set membership for something which is the underlying type, not yet elevated into the wrapper typedef. like an input which is string, which has a one-way relationship seen from int -> string via cmd/stringer + go:generate stringer, for example.

love using stringer to generate foo.String() impl, but still find mapping var somejsoninputmaybe string = "foo" cumbersome in the following:

//go:generate stringer -type=bar
type bar int

const ( 
    foo bar = iota
)
var somejsoninputmaybe = "foo"

fmt.Printf("somejsoninputmaybe ?= foo == %t", somejsoninputmaybe == foo)

perhaps im missing the boat somewhere herein? any thoughts appreciated.
might make a cmd utiliy to go:generate a map used to check membership w/ a defined slice of the enums... considering the code link above, something like:

func elevator(s string) (ACTION, error) {
    if value, ok := ACTION_value[s]; ok {
        return value, nil
    }
    return ACTION(-1), fmt.Error("couldnt find %s as registered in dictionary", s) // using iota+1 for the EMPTY ref in the code would make it more intuitive to handle the zero-val behaviour of ints
}

@mvrhov
Copy link

@mvrhov mvrhov commented May 21, 2021

@coip We are using github.com/dmarkham/enumer to generate just that. Along with that we get the various Marshal/Unmarshal methods and support for sql, case insensitive matches etc.. I don't think, that the official support for enums would provide all that. and if not it's better to leave it as is and use generators for additional support.

@coip
Copy link

@coip coip commented May 21, 2021

v cool, thanks for the link @mvrhov ! Just what I needed, of course it already exists. 🥂

@ysaakpr
Copy link

@ysaakpr ysaakpr commented Jan 9, 2022

Whats the status on adding enum to golang. There are many issues and this thread also has many conversations, and still there is no progress on this direction and no final conclusion. Can someone add or mark all the complexities involved in making Enum as a fundamental construct in Go, just to understand why its a topic that need this much wait and conversation

@Merovius
Copy link

@Merovius Merovius commented Jan 9, 2022

@ysaakpr I'd say the single biggest hurdle is lack of consensus that it should happen and if so, what specific features it should have. I'm pretty sure that if we had consensus on those two, it would be fairly easy to come up with a design accommodating those features.

But the assumption of your question is that it will happen and there is just some hurdle preventing it. I would really recommend against viewing any language feature through this lens. Not all languages will have all features and that is a good thing. We certainly might get typed enums at some point, if someone on the Go team decides that we really need them (just like it happened with generics). Until then, I'd personally recommend just assuming that it won't (just like people did with generics).

@ysaakpr
Copy link

@ysaakpr ysaakpr commented Jan 10, 2022

I would really recommend against viewing any language feature through this lens
I am not comparing features that are very complex in nature and not very much used through out. The enum are giving an ability to limit statically defined constants possible for a type. And to implement validation around this for every type that we create it always ends up writing un necessary code. Basic need of enum for me is

  1. Define static constants value possible for a type, compile time
  2. Ordinal around the possible values, manually assignable, with defaults
  3. String representation of the fields for more readability

Out of these 2 and 3 can be achieved using the current type definition, but the point 1 is not handled in the current type system, and according to me these three are if not there

@github-staff github-staff deleted a comment Jan 10, 2022
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 10, 2022

@ysaakpr As @Merovius said, there is no clear consensus on what properties enum types should have in Go.

For example, some people want the ability to loop over the valid values of the enum type, which is not on your list.

@ysaakpr
Copy link

@ysaakpr ysaakpr commented Jan 17, 2022

@ianlancetaylor Sorry, I forgot to mention that, when the values are fixed, ability to list/iterate over all the possible values of an enum is a must and which is intrinsic with the kind of this type. I understand there are multiple features required, but i am sure this lists is still limited. At least do we have a ticket on which we tracking all the requested behaviours on enum and we can use it for tracking, prioritising and categorising the features

@Merovius
Copy link

@Merovius Merovius commented Jan 17, 2022

@ysaakpr That's this issue.

Also, to repeat: There's also the question of if we want typed enums at all. There are people (like me) who consider the idea counterproductive or their value at least overstated. It also has semantic overlap with #19412 and it's not clear if we'd need both. And some features are contradictory and need to be weighed against each other.

@henryas
Copy link

@henryas henryas commented Mar 18, 2022

I don't know if this is going to help with the discussion, but for my own use, I usually use a tool to go-generate the following functions and methods for my enums:

  1. A function that returns the defined enum values. For example: func AllMemberType() []MemberType. This is used to enumerate the enum.
  2. A conversion to and from string.
  3. IsDefined method. To check if the enum is one of the defined values.

Out of the three items above, number one is the most useful.

I do not think a new type is needed. In fact, I think Go2 is a good opportunity to review existing features and select which one to clean and merge/redesign, and prepare a clean slate for the next iteration of Go. I don't think we should be aggressively adding features to Go2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Go2 LanguageChange NeedsInvestigation Proposal
Projects
None yet
Development

No branches or pull requests