This page looks best with JavaScript enabled

Go generics

 ·  🎃 kr0m

Generic functions in Go allow us to reuse the same function for two different input types. In this article, we will explain how they work with a simple example.


If you are new to the world of Go, I recommend the following previous articles:


First, let’s look at an example without generic functions where we have two functions that take a map and sum the values. These values can be either integers or floating-point numbers, so we need two different functions: SumInts and SumFloats.

vi generics.go
package main

import "fmt"

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

We run the code:

go run generics.go

Non-Generic Sums: 46 and 62.97

To create a generic function SumIntsOrFloats, in addition to the function’s input and return parameters, we also need to specify the type arguments, allowing it to operate with the indicated input parameter types.

Let’s compare the regular functions with the generic one:

func SumInts(m map[string]int64) int64 {
func SumFloats(m map[string]float64) float64 {
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {

As we can see, the first two accept the input variable m of type map (m map[string]int64 / m map[string]float64), where:

  • The key is of type String.
  • The value is of type int64/float64 respectively.
  • The return values are int64/float64 respectively.

In contrast, in the generic function, the input parameter is of type map (m map[K]V), where:

  • The key is of type comparable (K comparable), which allows any value that can be compared using ==, !=. Go requires that map keys be comparable .
  • The value is of type int64 or float64 (V int64 | float64).
  • The return value is of type int64 or float64 since V is (V int64 | float64).

We must keep in mind that we cannot restrict type arguments to just any group of values. This depends on the operations that the code performs. For example, if we allow integers and strings in the type arguments but then the code tries to perform arithmetic on the strings, the code will not compile.

The call from main to the generic function also changes; we must specify the data type of the input parameters. Let’s compare the initial code with the current one:

SumInts(ints),
SumFloats(floats),
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))

The final code using generics would be as follows:

vi generics2.go

package main

import "fmt"

// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))
}

We can see that when we run it, we get exactly the same result but with fewer lines of code:

go run generics2.go

Generic Sums: 46 and 62.97
If you liked the article, you can treat me to a RedBull here