Understanding Golang Generics with Examples
Easy tutorial for using generic types and functions in Go
In Golang version 1.18, we were introduced to a new feature called generic types in the language. With generics, we can declare and use functions or types that are written to work with any type of a set of types provided.
Simply, your generic functions and types will work with any number of types that you define them for, hence making them more useful in a broader sense in your Go program or project as a whole.
Let’s see what they’re all about in detail and how to utilize them effectively in our code.
Prerequisites
To follow this tutorial, you will need:
Go version
1.18
or greater installed. To set this up, follow my previous tutorial on installing and using common development utilities for Golang:
A simple use case
Imagine that you are in need of a function that can take the following kinds of inputs:
A set of key-value pairs of string-string maps
A set of key-value pairs of string-int maps
and output the following respectively:
A concatenated string of the values of the set of key-value pairs
A summed up int of the values of the set of key-value pairs
Going the conventional way
Now, if we go the normal way, we must write two functions for the use cases we need. Let’s write them down below:
// Concats the string values of map m.
func ConcatStr(m map[string]string) string {
var s string
for _, v := range m {
s += v
}
return s
}
// Concats the int values of map m.
func ConcatInt(m map[string]int) int {
var s int
for _, v := range m {
s += v
}
return s
}
As you see here, our two functions perform similar operations, it’s only the types they are operating on, that differ.
Now that we have these individual functions, we can easily call them from our main
function:
func main() {
// Initialize a map for the int values
map_string_int := map[string]int{
"first": 13,
"second": 26,
"third": 39,
}
// Initialize a map for the string values
map_string_string := map[string]string{
"first": "firstval",
"second": "secondval",
"third": "thirdval",
}
// call the two functions
fmt.Println("Concat outputs from the functions: \n",
ConcatInt(map_string_int), "\n",
ConcatStr(map_string_string))
}
Let’s run it and see the outputs, shall we?
$ go run main.go
Out:
Concat outputs from the functions:
78
firstvalsecondvalthirdval
Perfect! Works just as expected.
Now, imagine doing the same two operations: the string concatenation as well as the integer summation with a single, common function?
We can do that with generics in Go.
Writing a generic function instead
The first thing we need to do when writing a generic function is that we need to define a generic type for our parameters in ConcatInt and ConcatStr
functions.
This means that for the input maps, we need a way to represent the key value pairs generically.
In our generic function, we will define two type parameters (inside the square brackets),
K
andV
.We will also define the argument that uses the type parameters,
m
of typemap[K]V
. The function will return a value of typeV
.
Let’s define the function below:
// Concat generic function
func ConcatGeneric[K comparable, V string | int](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
Here, the comparable
type is used typically for maps in Go. Intended specifically for cases like these, the comparable
constraint allows any type whose values may be used as an operand of the comparison operators ==
and !=
.
Thus, declaring
K
ascomparable
is necessary so you can useK
as the key in the map variable.
Now that we have defined our generic function separately, let’s use it in our main function and print the results!
Append this snippet to the main
function:
// call the generic function with two kinds of inputs
fmt.Println("Concat with generic function: \n",
ConcatGeneric(map_string_int), "\n",
ConcatGeneric(map_string_string))
Now run:
$ go run main.go
Out:
Concat outputs from the functions:
78
firstvalsecondvalthirdval
Concat with generic function:
78
firstvalsecondvalthirdval
As we see now, the outputs are the same for both regular and generic functions!
We have thus defined our first ever generic functions with this example!
Bonus: using generic types as interfaces
The parameters we used as type for V
in the above generic function can instead be written as an interface
, as a way to reuse it later on in other functions, if need be.
We can define it like so:
type Concat interface {
string | int
}
And then, we can refractor our generic function:
// Concat generic function
func ConcatGeneric[K comparable, V Concat](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
Now, if you rerun the main.go
file, it gives the same output.
Excellent! That concludes our tutorial on generics. If you followed along thus far, do check out the GitHub project for the code.