This is not true, iota is stable in its ordering. https://go.dev/ref/spec#Iota
Real enumerations/discriminated unions is the one thing I consistently wish for in the Go annual surveys
See e.g. https://adaic.org/resources/add_content/docs/craft/html/ch05...
I'm not aware of a good online presentation focused exclusively on Ada's enumeration types and their various uses. It's not even singled out in the Rationale documents for the design of the language and the (3?) design revisions since the 1980 launch; maybe the AARM (Annotated Ada Reference Manual) has more focused discussions? I'm not sure, I haven't looked at these since ~20y ago.
One added benefit is it serializes/deserializes safely (even when you add / remove values), so you can persist and read back values without a problem - even to a different language.
I had the joy of a numeric ID (an int) getting a B added to the end of it to distinguish the product as being the "same" but sourced from another vendor... (and vendor is part of the system so this is layers of silly, but useful on the floor if there is a problem).
This is the down side of "fleets" of applications with differing degrees of type safety working in concert.
Should an enum be fixed, and its change need to be reflected in every system? Cause current go out of the box isnt that. Should it be open ended as your suggesting ... because a "new int value" can flow through the system in an "Unsafe" way depending on the permissiveness or quality of your code.
I dont like any of the answers, but I candidly dont have a lot of problems with the current enum system in go. Is it great, no. But if your validating at your borders/boundries (storage, api, etc) and being responsible I dont really give it much thought.
Rust's enums are great. No "auto" boilerplate if not mapping to an integer, exhaustive pattern-matching, sub-types etc.
Rust doesn't have enums. It has sum types – that for some reason it arbitrarily decided to call enums.
Sum types are great. There is a good case to be made that Go would benefit from the addition of sum types. But until that day there isn't much more you can do with enums. That's all enums are – a set of named constants.
Not the first time a word has been used for several nearly-unrelated concepts, and it won't be the last.
I make heavy use of rust and Python enums (Are they both misnamed?) Those + structs are generally the base of how I structure code.
The "enums" in the article also seem to be of the same intent. Is this a "no true Scotsman" scenario?
Some research implies the difference is a True Enum involves integer mapping, while a Sum Type is about a type system. I think both the Rust and Python ones can do both. (repr(u8) and .value for Rust/Python respectively)
The use case is generally: You have a selection of choices. You can map them to an integer or w/e if you want for serialization or register addresses etc, but don't have to. Is that a sum type, or an enum? Does it matter?
Another thought:
Maybe:
#[repr(u8)]
enum Choice {
A = 1
B = 2
}
Is an enum, while enum Choice {
A(C)
B(D)
}
Is a sum type?Oh, so it's a lot like Python then.
> This is fine however it is nothing but an integer under the covers this means what we actually have is:
Oh, so it's a lot like C++ then.
> But what you notice here is we have no string representation of these Enums so we have to build that out next
Have to!
Yes this still all sucks. (But at least there's ugly historical precedent!)
Golang is an absolutely ideal language for writing REST API microservices, that exchange JSON, with a practical and blue-collar mindset.
Plus it compiles to small-ish native executables. Which renders Docker superfluous in many uses cases, and also makes it well-suited for writing DevOps tooling (e.g. Docker, everything from HashCorp, etc).
It's not trying to out-cool Haskell and Rust on online message boards. But I would never in a million years evangelize either of those two languages for routine REST API work in most real-world shops, whereas I could suggest Golang without losing professional credibility.
At the same time, Go has generators built in and can generate enum tables, enum to strings, and other things they have shown. I am unsure why they didn't do it the "Go" way.
{ "enums": [ { "package": "cmd", "type": "operation", "values": [ "Escalated", "Archived", "Deleted", "Completed" ] } ] }
My point was the "Go" way to do this isn't parsing a custom format (like your JSON), but it's to use go generate.
You just declare an int type, and then a list of constants of that type.
People are complaining about 'iota' here, but I think it's slick and great. It combines so nicely with eliding types and values from subsequent const declarations:
type MyEnum int
const (
Value1 MyEnum = iota
Value2
Value3
...
)
Nice and simple. Most of the syntax is just the enum value identifiers. And it works well for bit flags too: type MyFlags int
const (
Flag1 MyFlags = 1 << iota
Flag2
Flag3
...
)
Most of the above syntax isn't specific to enums (so you're already getting a lot of other things from it). The only enum-specific syntax is iota and the eliding type/value rule.People seem to want their languages to have all sorts of guardrails, but I find many of these cumbersome. Go gives me the one enum guardrail I care about: The enums are different types, so I can't use a MyEnum as a MyFlag, or vice versa.
I've worked on giant Go codebases, with Go-style enums all over the place, and the lack of compiler-enforced exhaustive enum switches just hasn't been a problem. And it's nice to be able to use non-exhaustive switches, when you want them. Go is simple and flexible here.
The article criticizes Go incorrectly with statements such as these:
> This also means any function that uses these or a struct that contains these can also just take an int value.
> Anywhere that accepts an Operation Enum type will just as happily accept an int.
This is just not true. Here's an example you can run: https://go.dev/play/p/8VGufuxgK6b
The above example tries to assign an int variable to a MyEnum variable, and gives the following error: "cannot use myInt (variable of type int) as MyEnum value in variable declaration"
This error directly contradicts what is claimed in the article. Perhaps they mean that MyEnum will accept an integer literal, in which case I would argue that a guardrail here is silly, because again the problem just doesn't really come up in practice. Regardless, the author is not being very precise or clear in their thinking.
Is this a real problem? If there's a function signature that accepts `Operation`, the caller must explicitly cast the `int` to `Operation`. At that point, it's the caller's own fault.
So I'm not really following what this is solving. As demonstrated in the article, sometimes you want string constants, sometimes you want `iota`, other times you want `1 << iota`. I like that Go doesn't dictate which I have to use if I declare an "enum".
You would think that, but that isn't always the case: https://play.golang.com/p/Ze3pfNEVTVs
It's very easy to create an enum value that isn't actually in the defined range
I like philosophy and I read it as a total amateur. Naming is a big topic in modern philosophy [1] with a huge amount of depth. I think of it in terms of my naïve understanding of Wittgenstein's later work and the idea that the meaning of a word actually comes from its usage within the context of a set of collaborating agents.
If I say to a programmer "use a vector", that will mean something specific if we are writing C++ and I want to use a resizable array. And it could mean something totally different in the context of a 3d rendering engine.
I think of how often I see words like "Context", "Session", "Kernel" and all of their myriad uses.
So I see articles like this as just a pointless argument because we are crossing some boundary between distinct language games. The author of this article thinks "Enum" means one thing. But it is actually the case that "Enum" is unspecified outside of some particular context. And in this case, the author is bringing some outside context and trying to reuse it inappropriately.
In the wider context of programming languages, enum is fairly well defined concept. Features like being able to convert a value to a string and do exhaustive checking on switch statements are widely implemented. The iota feature in Go is clearly imitating C’s enum keyword. It is fair to compare Go’s built-in ability to declare an enum-like type against other language’s ability to declare the concept.
To be clear, I’m not saying every language has to have every feature. I’m just saying the lack of a feature in a language is not a sufficient reason to excuse its lack.
I am—for some reason—reminded of philosophers who go looking for metaphysical problems, disputes, etc. where there aren’t any.[1]
"model" is another one like that since I'm using it to refer to both data models and ML models. And "prompt" since it can be an LLM prompt or a terminal prompt for the user (this is a CLI tool).
Differentiating all these overlapping terms in ways that aren't super confusing is definitely a challenge.
What I'm saying is the thing he wants and the thing Go has may share a name but they aren't the same thing. Just like C++ has "vectors" and OpenGL has "vectors" that share some superficial similarities but are ultimately totally different things.
If someone wrote an article that said "OpenGL vectors suck" and then mentioned it missed a bunch of features available in std:vector as justification, most people would recognize this error and dismiss the discussion.
Oh and you don't have to use large power-hungry IDEs that don't integrate with any sort of config management to get a decent experience! (/hj)
If I ever learn Haskell it's over for y'all though.
(Agree with OP btw, using codegen to get the enums I want is a workable remedy for Go's lack of enums.)
//go:generate go-enum -type=State
type State int
const (
Unknown State = 0
Disconnected = 1
Connected = 2
)
which then generates a separate file with implementations such as: func (i State) MarshalJSON() ([]byte, error) { ...
[0] https://pkg.go.dev/github.com/searKing/golang/tools/go-enum#...Not a full typesafe enum type, the same clunky "enums" (assigned constants) available in C, but they bother to implement an auto-incremented counter.
So you can't depend on the enum for exhaustiveness warnings e.g. on switch statements, type checking, or correctness, but you do get a useless numeric association autogenerated with iota - so that you can lose the association if you re-order your enum values that you have serialized earlier and want to reload in the future.
Don't serialize to raw integers unless you absolutely have to. Serialize to a string value: it's future/oopsie proof and helps with debugging. The nature of iota is pushing people away from bad habits.
But yes, getting warnings about missing enums in switch statements is very handy. But Golang's type system never aspired to be as rigid and encompassing as C++, Haskell, Rust, etc.
Well, didn't have to aspire to all that to at least make an effort to be more helpful, especially in trivial aspects, like having an actual enumerated type, or an Optional/Error type...
Rigid makes it sound bad. I would suggest "reliable" instead.
iota is a great idea, especially if you have to define bit mask constants, e.g. 1 << iota. I wish other languages (yes, also those with enums) had it as well.
* no null safety
* working with errors
* poor type system
This is sort of like how network protocols can’t statically guarantee enums are valid either. When sending bits over the wire, you can send any bits you like. There can be “values reserved for future use,” but to deny their use, you need a runtime check.
A similar solution works in Go. A runtime check in a constructor function will fix it. The enum’s value would need to be returned as an unexported field in a struct, which is the only way to guarantee that it’s not writable, except by copying it from another valid value.
I don’t see a particular reason why Go couldn’t make this easier.
Im also curious now if the Typescript checker was written in a way that it could be adapted to new languages easily.
In the case of Goplus, it compiles to Go. Speaking of which, Vlang allows transpiling from Go and possibly to Go, but from and to C is more of their priority.
The intent of the Goplus author and contributors seems to be that their people could easily switch between both, but that their version is more feature rich.
Use uint64s with minimal bit overlap.
Maybe nice to include in this ultra-advanced enum libray
I understand the evolution of C, it made perfect sense back when it was invented. And the limitations were necessary due to the wide array of architectures and extremely limited computers of the time in every dimension (CPU speed, IO speed, RAM size, disk size, etc).
Many of those dimensions have been improved by several orders of magnitude, and both compilers and runtimes can afford to be comprehensive. Yet we get this ham-strung language out of the gate.
Very disappointing.
I have a far easier time delving in to previously unknown Go code for the first time compared to something like Scala (or even Java). Go is a solid language for those who value that and want to enable the experience for others.
Also, we have been doing computer language design for quite awhile now. This isn’t a new frontier. The deficiencies in Go aren’t in areas of “oh, we never thought of that!”, but are in very well known areas with known solutions.
I find Go code is obscured with house keeping code that isn’t necessary in better languages.
For example, to encode a JSON structure with a dynamic top-level key you need to write a custom marshaller OR marshal twice. That's... awful. Like bonkers level insane.
Go is not simple, it is idiotically designed to deliberately exclude common sense features that ironically makes it less simple and more error prone to code in and read Go.
Other languages are objectively better than Go for every imaginable use case. Rust is better for embedded. Kotlin is better for back end. I could go on.
The creator of Go is very open and candid that he thinks his target audience, Google Engineers, are too stupid to use "advanced" features like oh I don't know, sane error handling? and any number of basic things other languages have.
I know how cringe it is to start flame wars about programming languages, but srsly, Go, PHP, Perl, JS and a few others really are objectively worse (for every context and use case) than widely used alternatives.
Golang _has_ sane error handling. It just considers errors a normal and expected situation.
When you perform a http request, and the result is successful you expect the result to be assigned a variable, right? Then why would you expect non-successful outcome to be returned in a different way? Why is it different? Why do you unwind the stack? Something terrible happened? Definitely not, it's as real life as 200 OK.
For unrecoverable things golang has panics, and if you don't like the idiomatic way of handling errors, you can just throw them like exceptions.
Disappointing it maybe but productive it is not considering people can easily move to far better languages.
Hey! Just like in C! I digress, I think the issue is that the author comes from another language where enums are a thing. In go, they aren’t. Enums should be types. Types that don’t infer to an int. Use an interface. Be happy.
I'd be willing to bet that there's just a better way to do whatever the actual real-world example they want to achieve is (this was not entirely clear to me from the examples in the post).
Like I said though, that doesn't mean that (real) enums wouldn't be an even better way to do it than whatever the Go way is for a given problem, so I don't want to quibble too much since I think this is one of my biggest day-to-day complaints about Go, but it's worth pointing out that the premise can be flawed and that it's still a problem in the language, these two things aren't completely orthogonal.
TL;DR — Instead of pulling in a code generator and another library, it may be good to think of alternate ways to do the same thing without a lot of extra code footprint.
Which is exactly what they do in the post.
They still have every reason to complain about Go's oft suggested lame substitute.
Well, yeah, this is also the reason they deserve quite a bit of ridicule from actual Go users.
That’s two options. What are the others that are meaningfully different? You have to be able to deal with simple “sum types” in the sense of: this type could take on the value of one of these X predetermined constants. This requirement doesn’t disappear just because the language doesn’t directly support it.
Other languages either substitute enum with primitive type, string, or use strong type system tricks.
Go do duck typing, .. so..
So much so that much of the dotnet official stuff, ie asp.net, use static classes with string fields instead of enums.
Unfortunately that doesn't play well with libraries that have enum support like entity framework. PITA.
One saving grace is the ability to create extension methods on enums.
Developers with a background in other languages assume all enums' use cases need string representation. Well, no. They are needed sometimes, but not always.
The same with the ability to pass int to the enum. Author says:
> Anywhere that accepts an Operation Enum type will just as happily accept an int.
Well, this is simply not true. [1] You'll have to cast your int into your enum, which is totally fine if it's your intent. Granted, there are plenty of valid cases where you need validated input, especially for the public libraries. But hey, not every code is a publicly facing library, and not all need this validation. Why spend CPU and memory on something that probably won't be needed, and that can be implemented with the existing language primitives?
In the end, the author did a great job of solving his own requirements around enums and even wrote a code generator that helps him generate this for millions of enum types per second. :)
>Well, this is simply not true.
As comment above [1] pointed out `printOperation(2)` is still valid.
My understanding is that on a practical level, these kinds of issues arise from misusing types (or not caring about them) and naively putting variables of one type into the function that expects another. Examples with number constants typed in manually do not hold ground.
How long will it be this time until the Go devs accept that Java Enums are a safer and better abstraction over integers for the cases where you'd want an Enum? And that they allow something like EnumSet, which are type-safe bitsets, without everyone having to do that by hand?
Yeah it has evolved a bit since, but keeping the language simple is a worthwhile goal, so they didn't make rapid changes. It was intentional and thoughtful. If you want lots of language features, pick another language. I'll take my simple one.
Also: go enums do suck.
Python was the same way a few years back. People would give elaborate lectures on why Perl's features were so bad, only that they agreed to add many such features to Python within a decade, even at the extreme act of breaking backwards compatibility.
This is seems to be a common arc to so many things. When you start you are all about principles and as you age, you realise practicalities of every day life demand making lots of tradeoffs and deviations from founding principles.
You don't think that most of the complexity of modern languages is unjustified?
In some cases these statements have some merit, but, as in most cases, they demonstrate that the authors didn't really do their homework before making a language. Or they willfully ignored all of these issues. I don't know which is worse.
Go does have generics, though.
> Why don't you just use duck typing?
Go doesn't have duck typing. It has structural typing, which is not duck typing. Duck typing is dynamic typing (at runtime), structural typing is static (at compile time).
> Packages?
Go does have packages.
> Why don't you just use vendoring?
In Go it's recommended to use versioned modules, not vendoring.
That's exactly why I love it so much
To prove your point further, at the time I got the impression that most of the community was against that decision and didn't see the point in introducing generics in the language.
You make it sound as though the developers were opposed to generics, which isn't accurate. Perhaps some in the community expressed such sentiments. The plan has always been to possibly include generics at some point, which they then did.
To be explicit though, this comment is spot on. As someone who was part of the original Java development group when it was called "First Person Inc", I found the language "equivalence" concept debates the most interesting. For example, is Boolean a first class type? Or is it just a one bit integer? Is integer always signed? If you have 1 bit integers, 8 bit integers, 16, 32, and 64 bit integers, why not make 1024 bit integers a type too? Why is the number of bits fixed? If you want to be super radical, is it bits in an integer or is it digits? Is the integer type (radix, digits)? At one time there were discussions about real (signed), integer (unsigned), frac (fractional) and float (split).
And then a product manager type walks in and says something like "Love the architectural purity y'all are going for here but nobody else uses all these things so let's not make something that is so complicated we'll never ship it."
The author does a good job of exploring the characteristics of "good" enums, and I think it would be even better if it was understood that if your language is going to be used to implement finite state machines (which most programming languages do) then having strong protections against injecting invalid states into those machines is essential. If the language provides a way, that is great, otherwise you end up like the author did generating 30 - 50 lines of code for something that should take 3 - 5 lines to express.
[1] This is an inside joke, IYKYK
That's literally what enums are: A set of named constants.
You might be thinking of what is traditionally known as sum types, which some people have recently started calling enums[1]. Indeed, Go does not have sum types.
[1] Presumably because of Rust using the wrong term when specifying its sum types
The article is an advertisement for the authors own Go package that addresses the "problem."
func (o Operation) IsValid() bool {
if o == Unknown {
return false
}
return true
}
Why, oh why don't people just write return o != Unknown
This is so common in the code that I'm seeing on the Internet, on GitHub etc. Is it because people don't understand booleans?For this example we only have a single condition but as soon as you add more conditions it start to get out of hand.
I prefer the original code because it makes the codebase as a whole easier to read. But I don't think there are any 'hard facts' to support using either of these styles over the other in these simple cases.
Go was developed as a highly opinionated language in which this style of imperative code is apparently preferred. Similarly, the ternary operator shines as a way to use a single assignment to obtain a value that follows from a concise boolean expression, but the ternary operator is wholly omitted from Golang.
Although, being opinionated is not in itself a bad thing. If you want to create something, it helps to have a viewpoint that gives you something to say.
func (t operation) IsValid() bool { _, ok := strOperationMap[s] return ok }
Tbh it has been updated to us an array instead for string indexing.
Sorry to say, but that's the simplest truth. Many go years and years in this industry with little improvement to quality/succinctness/readability. If you cared, and tried, you would.
Programming skill is uniquely distinct from domain knowledge, by the way. e.g. all of the research code written by domain experts that is full on spaghetti.
type myEnum = (value1, value2, value3, value4)
Naturally that is too advanced and slows compile times."Enumeration types appear to be a simple enough feature to be uncontroversial. However, they defy extensibility over module boundaries. Either a facility to extend given enumeration types has to be introduced, or they have to be dropped. A reason in favour of the latter, radical solution was the observation that in a growing number of programs the indiscriminate use of enumerations (and subranges) had led to a type explosion that contributed not to program clarity but rather to verbosity. In connection with import and export, enumerations give rise to the exceptional rule that the import of a type identifier also causes the (automatic) import of all associated constant identifiers. This exceptional rule defies conceptual simplicity and causes unpleasant problems for the implementor."
I am confused by this assertion. I mean, if I had a module `Source` defining an enum:
type MyEnum = (value1, value2, value3, value4)
and another module wanted to import it: import ( MyEnum ) from "Source"
I don't see how I have automatically imported all of the associated constant identifiers. Unless he was assuming that this would force me to import `value1`, `value2`, etc. as distinct identifiers? But it seems these ought to be namespaced inside `MyEnum`, e.g. MyEnum.value1
And one could easily imagine an import syntax to selectively import Enum values if desired: import ( MyEnum: ( value1, value3 ) ) from "Source"
Of course, I'm just making up syntax, but I hope the meaning is clear.Oberon-07 minimalism doesn't make Go better.
Sarcasm ?
Cries in I18N...