Go 2 generics in 5 minutes

Alberto de Murga
5 min readFeb 5, 2021

--

Two generic gophers
Two generic gophers. Photo by Lukáš Vaňátko on Unsplash

The inclusion or not of generics in the Go language has been a long-standing discussion and cause of drama since the first appearance of the language in 2009. Rivers of ink have been poured in long discussions about if generics are good or bad, and if the language needs to support them or not. Up to this moment, the Go team has decided to leave generics outside the language.

However, with the announcement of the second version of the language, the Go team opened the discussion to add generics to the language. There have been different drafts about how to add generics finally settling down in what it seems a final design draft.

Generics in bullet points

The draft is quite long but not as dense as it could have been expected for such a document. It is important to highlight that the objective of the Go team is to create a backwards compatible design which addresses people’s needs for generics without making the language more complex than necessary.

The specification can be summarised in the following bullet points, that we will develop after. However, it is advisable for anyone interested in the topic to read the full document linked at the bottom of the article. We are assuming that the reader is familiar with basic Go concepts like functions, types, and interfaces.

  • Functions and types can have an additional type parameter list in front of the normal ones using square brackets to indicate the generic types used. These type parameters can be used like any other parameter in the rest of the definition and body.
  • The type parameters are defined using constraints, that are interface types. Constraints define the methods required and types permitted for the type argument and describe the methods and operations available for the generic type.
  • We can use type inference which will often permit omitting type arguments.
  • We have a special constraint named any which behaves similarly to interface{}, and a new package named constraints which will have commonly used constraints.

Defining and using generic functions

Functions can take an extra list of parameters surrounded by brackets instead of parenthesis, and they can be used as part of the definition and or the body. These type parameters behave like normal parameters and they follow the same rules as the traditional parameter. These parameters will have an “interface” named constraint as a type, or the special interface any. Types can also have type parameters when defined.

// https://go2goplay.golang.org/p/Z9e7O8ony21
type queue[T any] []T
q := new(queue[int])

Functions’ type parameters can be used in the body of the function, and types using type parameters can be used in the module without restriction. The requirement to use them is to pass a type argument that fulfils the interface. It is like assigning a variable of interface type: the type argument must implement the constraints of the type parameter, and the generic code can use only the operations permitted for the constraint or permitted for any type.

// https://go2goplay.golang.org/p/s8JCfu5qBKz
func Print[T any](s []T) {
for _, v := range s {
fmt.Print(v)
}
}
strings := []string{"Hello ", "world"}
Print[string](strings)
// Output: "Hello world"
nums := []int{1,2,3}
Print[int](nums)
// Output: 123

Defining constraints

Constraints are interfaces. You can expect the same features of interfaces on constraints, like embedding one constraint in other. Constraints have one addition to interfaces. Constraints may list explicitly types that may be used as type arguments by using the type keyword followed by a list of comma-separated types.

// https://go2goplay.golang.org/p/qWeRkYjjtKP
type SignedInteger interface {
type int, int8, int16, int32, int64
}

Constraints can be defined and exported as libraries, and the Go team stated that they would likely define and export a new standard library tentatively named constraints that would contain commonly used constraints.

The “any” constraint

The any type is like the type interface{}. It is a constraint that whatever type can fulfil. However, it still has a few rules it needs to abide. Once you assign it a type to it, you need to keep using the same one. You can declare variables, pass, or return variables to other functions, take the addresses… but always the same type. You can convert or assign values of those types to the type interface{} and use type assertion to convert them back. You can also use the type as a case in a type switch.

// Vector is a name for a slice of any element type.
type Vector[T any] []T
func (v *Vector[T]) Push(x T) { *v = append(*v, x) }
// T is []int
var v Vector[int]
// func (v *Vector[int]) Push(x int) { *v append(*v, int) }
v.Push(1)
// Output: [1]

Type inference in generics

In many cases, we can use type inference to avoid having to explicitly write out some or all the type arguments. If you pass an argument to a function and other types depend on it, they can be inferred without explicitly saying it. It works in the same way as when you use a variable of a certain type as a parameter instead of a function which has an interface as an argument.

func Map[F, T any](s []F, f func(F) T) []T { ... }  var s []int 
f := func(i int) int64 { return int64(i) }
var r []int64
// Can be used in all these ways// Specify both type arguments explicitly.
r = Map[int, int64](s, f)
// Specify just the first type argument, for F,
// and let T be inferred.
r = Map[int](s, f)
// Don't specify any type arguments, and let both be inferred.
r = Map(s, f)

This part gets extremely messy extremely fast, so I cannot stop recommending to real the whole specification where this section is explained in deep detail. The important part to understand is that in many cases we will not need to specify all the type parameters explicitly.

Disclaimer and references

This article is based on the following article, thread, and document. As it is a draft, it might change since the moment of publishing this article. To ensure corrected in all the examples, they have been extracted from the specification or from the Go 2 playground, where you can already head to if you want to test them by yourself.

--

--

Alberto de Murga
Alberto de Murga

Written by Alberto de Murga

Software engineer at @bookingcom. I like to make things, and write about what I learn. I am interested in Linux, git, JavaScript and Go, in no particular order.