golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
121.19k stars 17.37k forks source link

proposal: spec: add typed enum support #19814

Open derekperkins opened 7 years ago

derekperkins commented 7 years ago

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.

Things to Consider

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 commented 7 years ago

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

derekperkins commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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

derekperkins commented 7 years ago

I think that could be worked around

ianlancetaylor commented 7 years ago

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?

md2perpe commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

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

derekperkins commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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

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

vasiliicuhar commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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

Merovius commented 7 years ago

@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".

derekperkins commented 7 years ago

@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 commented 7 years ago

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.

jorng commented 7 years ago

I would love to see enums in Go. I am constantly finding myself wanting to restrict my exposed API (or working with a restricted API outside of my app) in which there are a limited number of valid inputs. To me, this is the perfect spot for an enum.

For example, I could be making a client app that connects to some sort of RPC style API, and has a specified set of actions / opcodes. I can use consts for this, but there is nothing preventing anybody (myself included!) from just sending an invalid code.

On the other side of that, if I am writing the server side for that same API, it would be nice to be able to write a switch statement on the enum, that would throw a compiler error (or at least some go vet warnings) if all the possible values of the enum are not checked (or at least a default: exists).

I think this (enums) is an area that Swift really got right.

Merovius commented 7 years ago

I could be making a client app that connects to some sort of RPC style API, and has a specified set of actions / opcodes. I can use consts for this, but there is nothing preventing anybody (myself included!) from just sending an invalid code.

This is a horrible idea to solve with enums. This would mean you can now never ever add a new enum value, because suddenly RPCs might be failing or your data will become unreadable upon rollback. The reason proto3 require that generated enum-code supports a "unknown code" value is that this is a lesson learned by pain (compare it with how proto2 solved this, which is better, but still very bad). You want the application to be able to handle this case gracefully.

jorng commented 7 years ago

@Merovius I respect your opinion, but politely disagree. Making sure only valid values are used is one of the primary uses of enums.

Enums aren't right for every situation, but they are great for some! Proper versioning and error handling should be able to handle new values in most of the situations.

jimmyfrasche commented 7 years ago

For dealing with external processes having an uh-oh state is a must, certainly.

With enums (or the more general and useful sum types) you can add an explicit "unknown" code to the sum/enum that the compiler forces you to deal with (or just handle that situation entirely at the endpoint if all you can do is log it and move on to the next request).

I find sum types more useful for inside a process when I know have X cases that I know I must deal with. For small X it's not hard to manage, but, for large X, I appreciate the compiler yelling at me, especially when refactoring.

Across API boundaries the use cases are fewer, and one should always err on the side of extensibility, but sometimes you do have something that can truly only ever be one of X things, like with an AST or more trivial examples like a "day of the week" value where the range is pretty much settled at this point (up to choice of calendrical system).

Merovius commented 7 years ago

@jimmyfrasche I might give you Day of the Week, but not AST. Grammars evolve. What might be invalid today, could totally be valid tomorrow and that might involve adding new node-types to the AST. With compiler-checked sum-types, this wouldn't be possible without breakages.

And I don't see why this can't just be solved by a vet-check; giving you perfectly suitable static checking of exhaustive cases and giving me the possibility of gradual repairs.

kongslund commented 7 years ago

I'm playing around with implementing a client for a server API. Some of the arguments and return values are enums in the API. There are 45 enum types in total.

Using enumerated constants in Go is not feasible in my case since some of the values for different enum types share the same name. In the example below, Destroy appears twice so the compiler will issue the error Destroy redeclared in this block.

type TaskAllowedOperations int
const (
    _ TaskAllowedOperations = iota
    Cancel
    Destroy
)

type OnNormalExit int
const (
    _ OnNormalExit = iota
    Destroy
    Restart
)

Hence I will need to come up with a different representation. Ideally one that allows an IDE to show the possible values for a given type so that the users of the client would have an easier time using it. Having enum as a first class citizen in Go would satisfy that.

abice commented 6 years ago

@kongslund I know it's not a perfect implementation, but I just made a code generator that might be of interest to you. It only requires that you declare your enum in a comment above the type declaration and will generate the rest for you.

// ENUM(_, Cancel, Destroy)
type TaskAllowedOperations int

// ENUM(_, Destroy, Restart)
type OnNormalExit int

Would generate

const(
  _ TaskAllowedOperations = iota
  TaskAllowedOperationsCancel
  TaskAllowedOperationsDestroy
)

const(
  _ OnNormalExit = iota
  OnNormalExitDestroy
  OnNormalExitRestart
)

The better part is that it would generate String() methods that exclude the prefix in them, allowing you to parse "Destroy" as either TaskAllowedOperations or OnNormalExit.

https://github.com/abice/go-enum

Now that the plug is out of the way...

I personally don't mind that enums are not included as part of the go language, which was not my original feeling toward the matter. When first coming to go I often had a confused reaction as to why so many choices were made. But after using the language, it's nice to have the simplicity that it adheres to, and if something extra is needed, chances are good someone else has needed it too and made an awesome package to help out with that particular problem. Keeping the amount of cruft to my discretion.

griesemer commented 6 years ago

Many valid points have been raised in this discussion, some in favor of enum support and also many against it (at least as far as the proposal said anything about what "enums" are in the first place). A few things that stuck out for me:

Which brings me to some questions about this proposal which I believe need to be answered before there can be any meaningful progress:

1) What are the actual expectations for enums as proposed? @bep mentions enumerability, iterability, string representations, identity. Is there more? Is there less?

2) Assuming the list in 1), can enums be extended? If so, how? (in the same package? another package?) If they cannot be extended, why not? Why is that not a problem in practice?

3) Namespace: In Swift, an enum type introduces a new namespace. There's significant machinery (syntactic sugar, type deduction) such that the namespace name doesn't have to be repeated everywhere. E.g., for enum values of an enum Month, in the right context, one can write .January rather than Month.January (or worse, MyPackage.Month.January). Is an enum namespace needed? If so, how is an enum namespace extended? What kind of syntactic sugar is required to make this work in practice?

4) Are enum values constants? Immutable values?

5) What kind of operations are possible on enum values (say, besides iteration): Can I move one forward, one backward? Does it require extra built-in functions or operators? (Not all iterations may be in order). What happens if one moves forward past the last enum value? Is that a runtime error?

(I've corrected my phrasing of the next paragraph in https://github.com/golang/go/issues/19814#issuecomment-322771922. Apologies for the careless choice of words below.)

Without trying to actually answer these questions this proposal is meaningless ("I want enums that do what I want" is not a proposal).

bep commented 6 years ago

Without trying to actually answer these questions this proposal is meaningless

@griesemer You have a great set of points/questions -- but labelling this proposal meaningless for not answering these questions makes little sense. The bar for contribution is set high in this project, but it should be allowed to propose something without having a PhD in compilers, and a proposal should not need to be a ready to implement design.

derekperkins commented 6 years ago

The introductory example (Enums in Go today) is misleading: That code is generated and almost nobody would write Go code like that by hand. In fact, the suggestion (How it might look like with language support) is much closer to what we actually already do in Go.

@griesemer I have to disagree. I shouldn't have left the full uppercasing in the Go variable name, but there are plenty of places where handwritten code looks nearly identical to my suggestion, written by Googlers who I respect in the Go community. We follow the same pattern in our codebase quite often. Here's an example pulled from the Google Cloud Go library.

// ACLRole is the level of access to grant.
type ACLRole string

const (
    RoleOwner  ACLRole = "OWNER"
    RoleReader ACLRole = "READER"
    RoleWriter ACLRole = "WRITER"
)

They use the same construct in multiple places. https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/bigquery/table.go#L78-L116 https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/storage/acl.go#L27-L49

There was some discussion later about how you can make things more terse if you're ok using iota, which can be useful in its own right, but for a limited use case. See my previous comment for more details. https://github.com/golang/go/issues/19814#issuecomment-290948187

griesemer commented 6 years ago

@bep Fair point; I apologize for my careless choice of words. Let me try again, hopefully phrasing my last paragraph above more respectfully and clearer this time:

In order to be able to make meaningful progress, I believe the proponents of this proposal should try to be a bit more precise about what they believe are important features of enums (for instance by answering some of the questions in https://github.com/golang/go/issues/19814#issuecomment-322752526). From the discussion so far the desired features are only described fairly vague.

Perhaps as a first step, it would be really useful to have case studies that show how existing Go falls (significantly) short and how enums would solve a problem better/faster/clearer, etc. See also @rsc's excellent talk at Gophercon regarding Go2 language changes.

griesemer commented 6 years ago

@derekperkins I would call those (typed) constant definitions, not enums. I'm guessing our disagreement is due to a different understanding of what an "enum" is supposed to be, hence my questions above.

griesemer commented 6 years ago

(My previous https://github.com/golang/go/issues/19814#issuecomment-322774830 should have gone to @derekperkins of course, not @ derekparker. Autocomplete defeated me.)

Judging from @derekperkins comment, and partially answering my own questions, I gather that a Go "enum" should have at least the following qualities:

Does that sound right? If so, what else needs to be added to this list?

derekperkins commented 6 years ago

Your questions are all good ones.

What are the actual expectations for enums as proposed? @bep mentions enumerability, iterability, string representations, identity. Is there more? Is there less?

Assuming the list in 1), can enums be extended? If so, how? (in the same package? another package?) If they cannot be extended, why not? Why is that not a problem in practice?

I don't think enums can be extended for two reasons:

  1. Enums should represent the full range of acceptable values, so extending them doesn't make sense.
  2. Just like normal Go types can't be extended in external packages, this maintains the same mechanics and developer expectations

Namespace: In Swift, an enum type introduces a new namespace. There's significant machinery (syntactic sugar, type deduction) such that the namespace name doesn't have to be repeated everywhere. E.g., for enum values of an enum Month, in the right context, one can write .January rather than Month.January (or worse, MyPackage.Month.January). Is an enum namespace needed? If so, how is an enum namespace extended? What kind of syntactic sugar is required to make this work in practice?

I understand how the namespacing came about, as all of the examples I mentioned prefix with the type name. While I wouldn't be opposed if someone felt strongly about adding namespacing, I think that is out of scope for this proposal. Prefixing fits into the current system just fine.

Are enum values constants? Immutable values?

I would think constants.

What kind of operations are possible on enum values (say, besides iteration): Can I move one forward, one backward? Does it require extra built-in functions or operators? (Not all iterations may be in order). What happens if one moves forward past the last enum value? Is that a runtime error?

I would default to standard Go practices for slices/arrays (not maps). Enum values would be iterable based on declaration order. At a minimum, there would be range support. I lean away from letting enums be accessed via index, but don't feel strongly about it. Not supporting that should eliminate the potential runtime error.

There would be a new runtime error (panic?) caused by assigning an invalid value to an enum, whether that be through direct assignment or type casting.

griesemer commented 6 years ago

If I summarize this correctly then enum values as you propose them are like typed constants (and like constants they may have user-defined constant values) but:

Does that sound about right? (This would match the classic approach languages have taken towards enums, pioneered some 45 years ago by Pascal).

derekperkins commented 6 years ago

Yes, that's exactly what I'm proposing.

Merovius commented 6 years ago

What about switch-statements? AIUI that is one of the main drivers for the proposal.

jorng commented 6 years ago

Being able to switch on an enumeration is implied, I think, since you can switch on basically anything. I do like that swift has errors if you haven’t fully satisfied the enum in your switch, but that could be handled by vet

Merovius commented 6 years ago

@jediorange I was specifically referring to the question of that last part, of whether or not there should be an exhaustiveness-check (in the interest of keeping the proposal complete). "No" is, of course, a perfectly fine answer.

The original message of this issue mentions protobufs as the motivator. I'd like to explicitly call out that with the semantics as given now, the protobuf-compiler would need to create an additional "unrecognized"-case for any enum (implying some name-mangling scheme to prevent collisions). It also would need to add an additional field to any generated struct using enums (again, mangling names in some way), in case the decoded enum-value isn't in the compiled-in range. Just like it is currently done for java. Or, probably more likely, continue to use ints.

derekperkins commented 6 years ago

@Merovius My original proposal mentioned protobufs as an example, not as the primary motivator for the proposal. You bring up a good point about that integration. I think it should probably be treated as an orthogonal concern. Most code that I've seen converts from the generated protobuf types into app level structs, preferring to use those internally. It would make sense to me that protobuf could continue unchanged, and if the app creators want to convert those into a Go enum, they could handle the edge cases you bring up in the conversion process.

griesemer commented 6 years ago

@derekperkins Some more questions:

derekperkins commented 6 years ago

What is the zero value for a variable of enum type that is not explicitly initialized? I assume it can't be zero in general (which complicates memory allocation/initialization).

As I mentioned in the initial proposal, this is one of the stickier decisions to make. If the definition order matters for iteration, then I think it would similarly make sense to have the first defined value be the default.

Can we do limited arithmetic with enum values? For instance, in Pascal (in which I programmed once, way back when), it was surprisingly often necessary to iterate in steps > 1. And sometimes one wanted to compute the enum value.

Whether you are using numerical or string based enums, does that mean that all enums have an implicit zero based index? The reason I mentioned before that I lean towards only supported range iterations and not index based, is that doesn't expose the underlying implementation, which could use an array or a map or whatever underneath. I don't anticipate needing to access enums via index, but if you have reasons why that would be beneficial, I don't think there is a reason to disallow it.

Regarding iteration, why is a go generate produced iteration (and stringify) support not good enough?

Iteration isn't my main use case personally, though I do think it adds value to the proposal. If that were the driving factor, maybe go generate would be sufficient. That doesn't help guarantee value safety. The Stringer() argument assumes that the raw value is going to be iota or int or some other type representing the "real" value. You would also have to generate (Un)MarshalJSON, (Un)MarshalBinary, Scanner/Valuer and any other serialization methods you might use to ensure that the Stringer value was used to communicate vs whatever Go uses internally.

derekperkins commented 6 years ago

@griesemer I think I may not have fully answered your question about the extensibility of enums, at least in regards to adding/removing values. Having the ability to edit them is an essential part of this proposal.

From @Merovius https://github.com/golang/go/issues/19814#issuecomment-290969864

any package that ever wants to change a set of enums, would automatically and forcibly break all their importers

I don't see how this is different than any other breaking API change. It's up to the creator of the package to respectfully handle BC, just the same as if types, functions or function signatures change.

ianlancetaylor commented 6 years ago

From an implementation perspective, it would be quite complex to support types whose default value was not all-bits-zero. There are no such types today. Requiring such a feature would have to count as a mark against this idea.

The only reason the language requires make to create a channel is to preserve this feature for channel types. Otherwise make could be optional, only used to set the channel buffer size or to assign a new channel to an existing variable.