This page looks best with JavaScript enabled

Go generics

 ·  🎃 kr0m

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


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


First, let’s see an example without generic functions where if the numbers to be summed are ints, we have to use the SumInts function, and if they are floats, we use the SumFloats function.

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 execute 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 on the specified input parameter types.

Let’s compare 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, an input parameter of type map(m map[K]V) is received, where:

  • The key is of comparable type (K comparable), this type allows any value that can be compared using ==, !=. Go requires map keys to 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 consider that we cannot restrict the type arguments to any specific group of values; this depends on the operations performed by the code. For example, if we allow integers and strings in the type arguments, but then the code attempts to perform additions with 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, generic:

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 executed, we obtain 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