Exploring Generic In Go

Pramono Winata
6 min readMar 26, 2021
Photo by Joseph Barrientos on Unsplash

Generic has been proposed a few years ago for Go and it has finally been accepted into the language early this year. And it has been scheduled to officially be part of the language by the end of this year.

With generic added, how will it really affect Go? Will it change how we code in Go? To really answer those question, we will need to take a look and trying on the proposed generic. Conveniently, the devs have provided us with a web compiler where we can experiment with the generic ourselves.

Generics? What Does It Really Change

To have generic means we will be able to have our function or data structure takes in several types that are defined in their generic form.To truly understand what does it means, let’s take a look at a very simple case.

Let’s say you need to make function that takes on slice and print it, you might make this type of function:

func Print(s []string) {
for _, v := range s {
fmt.Print(v)
}
}

Simple and what if we want to have the slice to be integer?You will need to make a new method for that:

func Print(s []int) {
for _, v := range s {
fmt.Print(v)
}
}

Those solutions might seems redundant, only changing the parameter for it. But currently that is how we will solve it in go without resorting into making it into some interface.

And now with generics, it will allow us to declare our function like this:

func Print[T any](s []T) {
for _, v := range s {
fmt.Print(v)
}
}

In that functions we are declaring two things:

  1. We have T which is the type of any (this keyword is specifically defined as part of generic, which indicates any type)
  2. And our parameter, we have variable s which the type is slice of T .

We will now able to call our method like this:

func main() {
Print([]string{"Hello, ", "playground\n"})
Print([]int{1,2,3})
}

One method for any type of variable, neat huh.That is only one of the very basic implementation for generic, looks very good so far. Let’s explore more and see how far generic can take us.

Limitation On Generic

We have seen what generics could do. We could specify a function that can take in any kind of parameter.

But the example i have given before was a very simple example. There is a limitation on how far generic could take us. Printing is pretty simple since golang could print out any type of variable being thrown into it.

What if we want to do more complex things, let’s say we have defined our own methods for a structure and want to call it:

package main

import (
"fmt"
)

type worker string

func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}


func DoWork[T any](things []T) {
for _, v := range things {
v.Work()
}
}

func main() {
var a,b,c worker
a = "A"
b = "B"
c = "C"
DoWork([]worker{a,b,c})
}

And you will get this:

type checking failed for main
prog.go2:25:11: v.Work undefined (type bound for T has no method Work)

It fails to run because the slice processed inside the function is type of any and it doesn't implement method Work hence making it fail to run.We can actually make it work though, by using interface:

package main

import (
"fmt"
)

type Person interface {
Work()
}

type worker string

func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}

func DoWork[T Person](things []T) {
for _, v := range things {
v.Work()
}
}

func main() {
var a,b,c worker
a = "A"
b = "B"
c = "C"
DoWork([]worker{a,b,c})
}

And it will print out this:

A is working
B is working
C is working

Well it works with interface, but just having interface without the generic works well also:

package main

import (
"fmt"
)

type Person interface {
Work()
}

type worker string

func (w worker) Work(){
fmt.Printf("%s is working\n", w)
}

func DoWorkInterface(things []Person) {
for _, v := range things {
v.Work()
}
}

func main() {
var d,e,f worker
d = "D"
e = "E"
f = "F"
DoWorkInterface([]Person{d,e,f})
}

This will give out this as result:

D is working
E is working
F is working

Using generic will only add in extra logic in the code and if using just interface is enough, I don’t see any purpose on adding generic in that code. Generic is still in its very early phase and at the moment i do see that generic has its limitation on doing complex processing.

Playing Around With Constraint

Before, we came upon type any for our generic constraint. Aside from that type, there are several other constraints that have been provided for us.

One of the constraint is comparable , let's take a loot at it:

func Equal[T comparable](a, b T) bool {
return a == b
}

func main() {
Equal("a","a")
}

Aside from that, we can also try to make our own constraint like this:

package main

import(
"fmt"
)

type Number interface {
type int, float64
}

func MultiplyTen[T Number](a T) T{
return a*10
}

func main() {
fmt.Println(MultiplyTen(10))
fmt.Println(MultiplyTen(5.55))
}

And i think that’s pretty neat, we can have one function for simple mathematical expression. Usually we will end up making two functions to take in that or using some reflection to have us only making one function.

It looks pretty neat doing it like that, but making own constraint still need to be experimented a lot. It’s still too early to know the limitation on making our own constraint like that. We should be careful not to abuse that and using it only if we are really sure it is needed.

Other Variations Of Use

Aside from using it as part of a function, you can also declare it as a variable like this:

type GenericSlice[T any] []T

And you can use that either as a parameter in function or you can make method out of that type:

func (g GenericSlice[T]) Print() {
for _, v := range g {
fmt.Println(v)
}
}

func Print [T any](g GenericSlice[T]) {
for _, v := range g {
fmt.Println(v)
}
}

func main() {
g := GenericSlice[int]{1,2,3}

g.Print() //1 2 3
Print(g) //1 2 3
}

The usage can vary depending on how you want to use it, all I can say is that we still need to experiment on this further to see what kinds of use case that we can utilize this generic on.

My Takes On Generic

Generic is still in its very early phase (It’s not even out yet!), but i’m pretty impressed on how it is made. There is not so many complicated terms and libraries with this generic implementation which really embedded Go strong point, which is the simplicity.

There are several cases that i can already see that using generic will be better (like the case with multiply method). One thing that a lot of people seem to be confused about is that generic might be a replacement for our usage of interface (both interface{} type and Interface implementation).

My advice is that don’t think of generic as a replacement for anything. Generic is just another tool provided for us in our coding life. Also, Generic might looks fancy and cool to try and put in every block of your code, but my take on this is don’t abuse it, use it whenever it’s needed, not whenever it can fit.

And that’s it. Thanks for reading my article and i truly hope it can become useful for you.Lastly, shout-out to this site, where i took a lot of reference from and it explains a lot of story regarding the generics on Go.

Have fun with Generic!

--

--