Generic in Golang

Finally, After a long-running debate, Generic is going to add to Golang. Let's take a look at it

This year started with new news from the golang team, "Adding Generic!".

If you have familiar with languages like Java or C#, you already familiar with the concept of Generics. For example, let's talk about sorting an array.

package main

import (
    "fmt"
    "sort"
)

func main() {
    f := func(msg string, v []int) {
        fmt.Println(msg)
        for _, x := range v {
            fmt.Printf("%d ", x)
        }
        fmt.Println()
    }
    v := []int{10, 20, 100, 2, -1, 80}
    f("before sort", v)
    sort.Ints(v)
    f("after sort", v)
}

In the above code, we just used the sort package to sorting a slice of integers and print them out.

Everything looks good, right? So what if we wanted to sort a slice of other numeric types? The sort package has another function called Float64s (instead of Ints) but it's definitely not enough because we might need to do some sorting operation on other numeric types like int32.

So one approach is to define one function for each numeric type but we all know it's not the best idea! it's the place that Generic comes to picture.

Here's the definition of Generic in Wikipedia :

Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.

So the point of generic is, specify the type of the variable later and provide it as a parameter. That's exactly what we need for example in our sort function. The only thing that we need is to make sure our input comply our conditions. for example in the sorting scenario, it must be numeric.

Let's see how we can use generic in golang:

func Bored[T any](s ...T) T {
    return s[0]
}

Bored function gives a slice of T as input and returns the first item in the slice as output. So WTH is T? first thing first, let's see how to use our Bored function:

func main() {
    output := Bored("Hello", "Generics")
    fmt.Println(output)
}

That's great, a generic function is used exactly like a normal function! so let's talk about T: In this case, T is string. so what's the difference between generic and the old-fashioned go interface{}? I'll explain it with an example.

func Bored(s ...interface{}) interface{} {
    return "I am string"
}
func main() {
    output := Bored(1,1.1,"hello", struct{}{})
    fmt.Println(output)
}

The difference is, interface{} could be anything but in the first function the type of T determined while passing the first element to it. if it's a string, the rest of them also must be a string. otherwise, you'll get an error while compiling the application.


func Bored[T any](s ...T) T {
    return s[0]
}

func main() {
    output := Bored("Hello", "Generics", 1)
    fmt.Println(output)
}

output:

type checking failed for main
prog.go2:11:39: default type int of 1 does not match inferred type string for T

It's nice, isn't it? for Java or C# developers something like Repository<T> is pretty familiar which T is almost always one of the application's entities.

Back to Golang, the other type (instead of any) that introduced now, is comparable. It means the item must have the capability of being in equality operation.

package main

import (
    "fmt"
)

func main() {
    fmt.Println(Equal(1, 1)) // true
    fmt.Println(Equal(2, 1)) // false
}

func Equal[T comparable] (input1, input2 T) bool {
    return input1 == input2
}

and same previous example we got a compile-time if pass multiple data type to this function:

func main() {
    fmt.Println(Equal(1.1, 1))
}
/*
output: type checking failed for main
prog.go2:8:25: default type int of 1 does not match inferred type float64 for T
*/

I think the golang team provides some ways for developers to define the restriction of the type T themselves. for now, I just have seen any and comparable, if you know more typed parameter please share them in the comment section;