3 Things Go Needs Right Now More Than Generics
My suggestions to make Go an even better language

Don’t get me wrong — I like generics, and I’m excited to see what impact they will have on the Go language and its libraries. However, it is clear that agreeing on the specification and implementing generics has been a massive undertaking for the Go core team, at the unavoidable cost of other language developments.
I work daily with Go, and while the lack of generics can occasionally be a pain point, mostly it can be worked around with some combination of either abusing interface{}
or manually generating the non-generic code. On the other hand, there are other pain points I feel much more often, and maybe other gophers do too…
1. Enums
These days it’s surprising to see a statically typed, modern, and production-ready language without any support for enums. For any readers not familiar with enums from other languages, put simply they are a way of declaring a custom type that may take one of a finite number of possible values. For example, you might want to declare a PrimaryColour
enum with possible values Red
, Yellow
, or Blue
.
In fact, you can think of all primitive types in Go as enums in disguise. Most obviously bool can be thought of as an enum that can only be true or false.
However, this also applies to other primitive types too. A byte can only represent the numbers 0–255 in the 8-bit range, an int32 can represent all possible signed integers in the 32-bit range, a pointer can represent all possible memory addresses in a (usually) 64-bit address space. So really, an enum is a tool to create your own primitive-like types.
Enums have real-world applications very often in programming — how many times do you use an API which requires you to specify one of a list of options?
For example, recently I was using an API that requires a SortOrder
parameter to specify whether results should be sorted in ascending or descending order — this is a perfect use-case for a two-valued enum.
There are two common workarounds for the lack of a dedicated enum type in Go. The first is to define a type based on an integer and create some named constants using iota to represent the enum values:
However, this approach has a couple of significant issues. Firstly, it is not an obvious way to declare an enum if you are at all familiar with their use in other languages.
Secondly, since the type is based on an int there is nothing at the type level saying that your pseudo-enum can only take the enum values you want — it can take any possible value that the integer can represent. It is very easy to cast any integer constant to your enum type — which is almost never what you want!
This possibility means that every time you want to switch on your enum’s possible variants, you always have to consider the possibility that a totally unexpected value has crept in. Even if you know it to be totally impossible, the compiler does not know that.
The second workaround is to give up on type safety entirely and just use string constants to represent various constants. This is doubly sad as not only do you have the same issue mentioned above but you also have the extra performance cost of comparing strings, as opposed to fast integer comparisons.
If go had an enum
keyword to complement the existing struct
, not only would declaring enums be much simpler and more intuitive, but the compiler would be able to strictly enforce that only the declared enum variants are allowed, meaning we can drop the default: error-handling cases from our switch statements for cleaner code.
2. Universal nil
How and when to correctly use nil is a fairly confusing and arbitrary thing to learn when you are new to Go. In short, nil is a value that may be used for pointers and certain other pointer-like objects including slices, maps, and interface types. It is the zero value for each of these types and represents an uninitialized value that will mostly blow up if you try to dereference it in any way.
There are some exceptions — a nil slice is mostly equivalent to a slice of 0 lengths, except when it’s not. You may call methods on a nil pointer, though depending on the type this will likely result in a nil pointer exception if the specific method does not check for the possibility of being passed nil. A nil map may be read from and acts the same as an empty map — but it will panic if you ever try to write to it.
Whether languages should even have nil pointers at all is a cause of some debate, but they are sufficiently embedded in Go’s type system that we are never getting rid of them now. A major reason for this is the requirement for all types to have a valid zero value — the value a variable will take if declared with the var
keyword without an assignment, or as a field in a struct that is not otherwise specified at construction time. It would be highly wasteful if the zero value for a pointer automatically allocated an object to point at, hence we have nil.
The issue is that, while all types in Go have a zero value, only some of these may be represented by nil. Generally, nil can be assigned to pointers or pointer-like objects including maps, slices, and interfaces. The zero value for any integer is 0, the zero value for strings is an empty string, and for bools it is false. In particular, there is no shortcut for the zero value for a struct value (not a pointer) — you have to specify the struct name followed by an empty set of braces. This quickly gets tedious if writing a function that returns a struct type and an error value, when performing many boilerplate error checks.
Sure, you could just return a pointer to that struct instead to be able to use nil instead — but maybe it makes more sense for the calling code to receive a value instead of a pointer. Maybe you want to save the heap allocation from returning a pointer value.
The way I see it, there is a simple solution staring us in the face — make nil
a placeholder for the zero value for any type, including structs as well as primitive types like integers and strings. This means that we no longer have to always return structs as pointers for the sake of simple error handling. Sure, it might be a little odd to see nil used in place of 0 for integer values, but I think this is a small price to pay — besides, it can be restricted to only be used in simple assignments or return statements and not as part of arithmetic expressions.
3. Concise Error Handling
Possibly the most controversial — Go is well-known for its highly verbose error handling, characterized by repetitive if
statements.
Compared to languages that support exceptions, Go’s verbose error handling is much more explicit and makes it easier to trace the path that errors can take through your program. However, this comes at the cost of taking up 4 times as many lines of code — that’s not a great signal-to-noise ratio!
I believe that there is an alternative approach, which keeps the explicit passing of errors as values instead of exceptions, but without requiring 4 times as many lines of code. Taking inspiration from Rust, Go could introduce a new ?
operator as a shortcut for boilerplate if err != nil {...}
code.
The basic idea of this ?
operator would be to automatically insert the if err != nil { return nil, err }
boilerplate. In the case when err
is nil, the expression takes the value of the first return object, allowing us to assign the result to a variable.
It may not be quite as verbose as writing out the if
statement in full, however I personally think that adding a ?
operator would still be a good trade-off in terms of dramatically reducing the number of lines of code in most typical Go programs.
Summary
Go is a great language but I think it still has a lot of room to grow. What do you think of these three suggestions? Any better ideas?