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
.
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:
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:
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:
Generic Sums: 46 and 62.97