Dwi Wahyudi
Senior Software Engineer (Ruby, Golang, Java)
In this post, I’m going to demonstrate the newly released generics feature in Go 1.18."
Overview
What is generics? Simply put, generics is parameterized type. We can create functions/methods that receive types.
In comparison, in the past, if we’re going to create a function/method to sum a slice of floats, someday later if we want to sum a slice of integers, we need a whole different function/method.
With generics (since Go 1.18), we only need 1 function that receive suitable types, and operate based on that type.
Before Go 1.18
First, let’s see such case in older version of Golang. Here below sumFloats
and sumInts
sum some respective data type of data.
package main
import "fmt"
func main() {
floatSum := sumFloats([]float64{3.3, 10.0, 12})
intSum := sumInts([]int64{3, 4, 5})
fmt.Println(floatSum)
fmt.Println(intSum)
}
func sumFloats(numbers []float64) float64 {
var result float64
for _, eachNum := range numbers {
result += eachNum
}
return result
}
func sumInts(numbers []int64) int64 {
var result int64
for _, eachNum := range numbers {
result += eachNum
}
return result
}
Output is:
25.3
12
As we can see, Go is statically-typed language, without generics, we’ll need to create different function for different data type.
Since Go 1.18
Now, let’s see how we handle this with generics.
package main
import "fmt"
func main() {
sumFloat := sumNumbers([]float64{3.3, 10.0, 12})
sumInt := sumNumbers([]int64{3, 4, 5})
sumInt8 := sumNumbers([]int8{1, 100, 23})
fmt.Println(sumFloat)
fmt.Println(sumInt)
fmt.Println(sumInt8)
}
func sumNumbers[T int64 | float64 | int8](numbers []T) T {
var result T
for _, eachNum := range numbers {
result += eachNum
}
return result
}
Output is:
25.3
12
124
One method, sumNumbers
, that receives int64
, float64
and even int8
data types.
Look at that syntax: [T int64 | float64 | int8]
. This means this function impose a constraint for the param type, only int64
, float64
and int8
, nothing else.
If we want, we can declare these constraint as a type for later reuse, export it, or not, your choice.
Do you think we can add string
data type to that constraint? What if we change that method to subtraction (-)?
type SomeNumbersType interface {
int64 | float64 | int8
}
//... skipped for brevity
func sumNumbers[T SomeNumbersType](numbers []T) T {
var result T
//... skipped for brevity
Go developers has made an experimental package for some generics type. Here it is: constraints package. We can see that there’s Ordered
type which contains numbers data type and string, this means that if we want to create a sorting method/function we can use such type.
Numbers and string can be compared with <
, >
, ==
and !=
and even +
(appending). But string can’t operate on -
.
any
Type
There’s another generic type, any
, which is not a constraint at all, because a function that receive T any
basically receive any data type to operate, but because it’s typed, it’s different with interface{}
in the sense that T any
will coerce the type inside the function/method. But the case for any
is also limited, because common traits among primitive data types and structs are few (printing to terminal for example).
From the outside of the function/method, any
might look like interface
, but once runtime gets inside the function/method, the type will be coerced.