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.