- read

The long awaited Go feature: Generics

Axel Dietrich 18

Since its birth, Go has been praised for a lot of reasons but criticized for others, being the main pain point the topic of this story: Generics. Or rather the lack of them.

First of all, the reason behind this decision was to keep the language simple, as it is Go’s philosophy.

From Go’s FAQ:

Go was intended as a language for writing server programs that would be easy to maintain over time. (See this article for more background.) The design concentrated on things like scalability, readability, and concurrency. Polymorphic programming did not seem essential to the language’s goals at the time, and so was left out for simplicity.

[…]

Generics are convenient but they come at a cost in complexity in the type system and run-time. We haven’t yet found a design that gives value proportionate to the complexity, although we continue to think about it. Meanwhile, Go’s built-in maps and slices, plus the ability to use the empty interface to construct containers (with explicit unboxing) mean in many cases it is possible to write code that does what generics would enable, if less smoothly.

What are Generics?

Sometimes it’s useful to implement data structures or algorithms that can work with any data type, such as a function that performs an operation with every element of an array, regardless of the data type. For example, if we want to iterate over an array of elements, printing on console each one.

Until now, the only way to do something like that was this:


func Print(arr []interface{}) {
for _, elem := range arr {
switch o := i.(type) {
case int64:
fmt.Printf("%5d\n", o)
case float64:
fmt.Printf("%7.3f\n", o)
case string:
fmt.Printf("%s\n", o)
default: // covers structs and such
fmt.Printf("%+v\n", o)
}
}
}

Not very practical, in my humble opinion.

Generics has been the top requested feature to Go development team, as shown in the following chart extracted from the Go 2020 Survey:

Generics is by far the most requested feature

Requests have been heard and Generics are being released in version 1.18 (expected to come out February 2022).

That doesn’t mean we can’t have fun until then. The release date is for production-ready versions and we now have access to the beta 2 (you can learn how to install it here). Without further ado, let’s get into Go Generics!

Defining a generic function

To define a generic function we have to place the generic type identifier followed by the word any between square brackets, after the function name and before the parameters.

This way, we can pass any slice with any datatype and it will get printed. Let’s try with a slice of int, another one of string and a final one with a struct.

And we get the following console output:

1
2
3
4
hello
i'm
using
generics
{Matthew}
{John}

As we can see, every data type got printed with the same function. Yay!

Constraints

There’s a way to limit what data types our generic function can admit by using constraints. For example, we may want to limit the admitted types only to data types that can be converted to strings. For this, we create an interface Stringerwith the function String() string and make the generic datatype T Stringer . This way, the function will only accept types that implement the String() string function.

Console output:

Matthew Johnson
John Johnson

Comparable Constraint

We can declare a comparable generic type to restrict parameters to data types that can be used with the operands == and != . Let’s make a generic function that counts occurrences of a value in an array and execute it with an array of string and then with an array of int:

Console output:

3
2
2
3

Several types

We can declare a function that accepts only certain data types:

Console output:

6
14.3

We can also declare an interface with a list of data types:

type Addable type {
int | int32 | int64 | float32 | float64 | uint | uint32 | uint64
}

But if we perform an addition between an Addable data type and an alias

type:

We get the following error:

./main.go:17:17: myInt does not implement Addable (possibly missing ~ for int in constraint Addable)

As the error message says, we can resolve this by simply adding ~before the list of data types in the Addable interface. This way any alias type implements Addable.

And now it works:

9

Mixed

Constraints can also be mixed together in a new interface

type ComparableStringer interface {
comparable
String() string
}

This way, a generic data type T ComparableStringer will only accept types that implement the String() string method and are comparable .

I left out some topics regarding Go Generics to make this a light read, so make sure you keep on learning about them!

Happy coding!