What are Go Generics & When Should I use Them?

mattjamesboyle

Matt Boyle

Posted on March 16, 2022

What are Go Generics & When Should I use Them?

Yesterday Go 1.18 was released!

1.18 has been eagerly awaited by the Go Community as it contained the official support of generics in Go as well as a whole host of other features which I hope to cover off in other blog posts in the future. You can read more about the release in the official release notes here.

If you like this blog post and want to support me to write more whilst learning more about Go, you can check out my site bytesizego.com. I have both free and paid courses and writing.

For this blog post, we are going to focus on the following bullet from the notes:

> The syntax for function and type declarations now accepts type parameters.

Type Parameters? What are They?

Type parameters are Go's way to support generics. Generic's allow developers to write "general" code that works for many different data types without having to specify that at the point of code creation. I know that is confusing, so let's look at an example.

Before Go 1.18, lets say we had the following slices we wanted to sum together:

func main() {
    intsToAdd := []int{1, 2, 3, 4}
    floatsToAdd := []float64{1.1, 2.2, 3.3, 4.4}
}
Enter fullscreen mode Exit fullscreen mode

We would need to write the following code:

func sumInts(nums []int) int {
    var res int
    for _, num := range nums {
        res += num
    }
    return res
}

func sumFloats(nums []float64) float64 {
    var res float64
    for _, num := range nums {
        res += num
    }
    return res
}

func main() {
    intsToAdd := []int{1, 2, 3, 4}
    floatsToAdd := []float64{1.1, 2.2, 3.3, 4.4}

    fmt.Println("ints", sumInts(intsToAdd))
    fmt.Println("floats", sumFloats(floatsToAdd))
}
Enter fullscreen mode Exit fullscreen mode

Which outputs:

ints 10
floats 11
Enter fullscreen mode Exit fullscreen mode

This works and there is nothing wrong with it at all. In fact, I imagine some teams will choose to continue to write code like the above due to its clarity.

However, it does lead to a lot of very similar functions as you can see.

With the introduction of generics, we can write this much more succinctly as the following:

func main() {
    intsToAdd := []int{1, 2, 3, 4}
    floatsToAdd := []float64{1.1, 2.2, 3.3, 4.4}

    fmt.Println("ints", sumNumbers(intsToAdd))
    fmt.Println("floats", sumNumbers(floatsToAdd))
}

type Number interface {
    int | int64 | float32 | float64
}

func sumNumbers[n Number](nums []n) n {
    var res n
    for _, num := range nums {
        res += num
    }
    return res
}
Enter fullscreen mode Exit fullscreen mode

This looks pretty confusing to me since I'm not used to it, but I'm hoping over time I get more comfortable reading code like this.

Let's step through it.

Firstly we declare an interface which is going to be our type constraint:

type Number interface {
    int | int64 | float32 | float64
}
Enter fullscreen mode Exit fullscreen mode

Here we are saying anything that is an int, an int64, a float32 or float64 is a Number. Whenever we reference Number, it must be one of these things. This is similar to how we have used Go interfaces in the past.

In the square brackets below we add our type constraint Number and call it n. This means whenever we reference n we are referring to the Number type. The Compiler will do some clever work at compile time to figure out everything else for us.

func sumNumbers[n Number](nums []n)
Enter fullscreen mode Exit fullscreen mode

Now we have told our function that anytime we reference n we are referencing either an int int64, float32 or float64, we can fill in the rest of the code.

Please note that generics does not make the following valid.

// trying to mix ints and floats
NumsToAdd := []Number{1.1,-3, 2.2, 3.3, 4.4}
Enter fullscreen mode Exit fullscreen mode

This is because our Number interface contains a constraint types which means it cannot be used like this (confusing I know).

When should I use Generics?

There is a video from Go team engineer Ian Lance Taylor here which does a great job of walking through use cases. I highly recommend watching it.

In general, it is advised to start with simple functions and only try and write generic functions once you have wrote very similar code 2 or 3 times. In the example above, I would not have considered writing a generic function until I had wrote the sumFloats function and realised how similar it was to the sumInts function.

Hope you found this useful! You can find me on twitter here

💖 💪 🙅 🚩
mattjamesboyle
Matt Boyle

Posted on March 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related