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