Generics in Go

Ahmad Berahman
Geek Culture
Published in
5 min readApr 27, 2021

--

The Go team has released a draft design for type parameters, which are colloquially referred to as generics. It’s expected to be included in Go 1.18. While there might still be last-minute changes.

Go is a statically typed language, which means that the types of variables and parameters are checked when the code is compiled. Built-in types (maps, slices, channels) and functions (such as len, cap, or make) can accept and return values of different types, but user-defined Go types and functions cannot. If you are familiar with Java, C++, or another language with some variety of generics, the simplicity of Go’s type system has probably caused a few frustrations.

If you are familiar with dynamically typed languages, where types are not evaluated until the code runs, you might not understand what the fuss is about generics, and you might be a bit unclear on what they are. It helps if you think of them as “type parameters.” We are used to writing functions that take in parameters whose values are specified when the function is called. Similarly, we create structs where the value for the fields is specified when the struct is instantiated. Generics is the concept that it is sometimes useful to write functions or structs where the specific type of a parameter or field is specified when it is used.

Since the first announcement of Go, there have been calls for generics to be added to the language. Russ Cox, the development lead for Go, wrote a blog post in 2009 to explain why generics weren’t initially included. Go emphasizes a fast compiler, readable code, and good execution time, and none of the generics implementations that they were aware of would allow them to include all three. After a decade of studying the problem, the Go team thinks it has a workable approach outlined in the Type Parameters-Draft Design.

We’ll see how generics work in Go by looking at a simple stack. If you were to implement a stack for any type without generics, here’s what you’d do:

We can use it like this:

This works and prints out 30 true, but you can insert a value of any type into this stack, and if you want to do more than print out the value returned by Pop, you need to use a type assertion to convert it to the type that was inserted. Let’s see how Go generics make this stack type-safe:

There are a few things to note. First, we have [T any] after the type declaration. Type parameters are placed within brackets. They are written just like variable parameters, with the type name first and the type bound second. You can pick any name for the type parameter, but it is customary to use capital letters for them. Go uses interfaces to specify which types can be used. If any type is usable, this is specified with the new universe block identifier any, which is exactly equivalent to interface{}, but is only valid within a type constraint. Inside the Stack declaration, we declare vals to be of type []T instead of []interface{}.
Next, we look at our method declarations. Just like we replaced interface{} with T in our vars declaration, we do the same here. We also refer to the type in the receiver section with Stack[T] instead of Stack.
Finally, generics make zero value handling a little interesting. In Pop, we can’t just return nil, because that’s not a valid value for a value type, like int. The easiest way to get a zero value for a generic is to simply declare a variable with var and return it, since by definition, var always initializes its variable to the zero value if no other value is assigned.

Using a generic type is very similar to using a nongeneric one:

The only difference is that when we declare our variable, we include the type that we want to use with our Stack, in this case int. Now, v has a type int and not interface{}, so you can use it without a type assertion. Furthermore, if you try to push a string onto our stack, the compiler will catch it. Adding the line:

produces the compiler error:
cannot convert “nope” (untyped string constant) to int

Since generics are not officially released yet, they are not supported on The Go Playground. However, there’s a temporary “Go2Go” Playground where you can try out the generic stack.
Let’s add another method to our stack to tell us if the stack contains a value:

Unfortunately, this does not compile. It gives the error:
cannot compare v == val (operator == not defined for T)

Just as interface{} doesn’t say anything, neither does any. We can only store values of any type and retrieve them. To use ==, we need a different type. Since nearly all Go types can be compared with == and !=, a new built-in interface called comparable is defined in the universe block. If we change our definition of Stack to use comparable:

we can then use our new method:

This prints out:

You can try out this updated stack.

In the resource of this post, you can learn more about Go Generic with different titles, For instance: Use Type Lists to Specify Operators, Generic Functions Abstract Algorithms, Type Lists Limit Constants and Implementations

Conclusion

Adding generics clearly changes some of the advice for how to use Go idiomatically. The use of float64 to represent any numeric type will end. We will no longer use interface{} to represent any possible value in a data structure or function parameter. You can handle different slice types with a single function. But don’t feel the need to switch all of your code over to using type parameters immediately. Your old code will still work as new design patterns are invented and refined.

Since there are no production implementations of generics, it’s hard to say how they will affect performance. It’s likely that there will be some impact at both compile-time and runtime. As always, the goal is to write maintainable programs that are fast enough to meet your needs.

We took a look forward at generics and how they will change how we use Go to solve problems. The implementation isn’t released yet, so there’s still a possibility of additional changes, but they are likely to be minimal.

--

--