You probably know that Go is a statically typed language, which means that every variable in your program must have a predefined type. For example, when we define a function, we need to specify the types of its parameters, like in the following code:

func Print(s string) {
fmt.Println(s)
}

Here, we have defined a parameter named “s” with the string type. We can define any number of additional parameters with any types, such as float64, int, or structs. However, the problem arises when we want to pass a number(or other types) to this function because numbers are of type int, and we can’t assign an int to a variable of type string. Previously, we used to define the type of the “s” parameter as an interface, which partially solved this problem but still had many limitations.


Generics

This is where we came up with something called generics to solve this problem.

Let’s try it out with some code:

func PrintAnything[T any](thing T) {  
     fmt.Println(thing) 
}

Isn’t it better? We defined a generic named “T” that can take any type, and we defined a parameter of the generic type “T.” This way, we can pass anything to this function to print it easily:

PrintAnything("Hello")
PrintAnything(42)
PrintAnything(true)

When we call this function, the type of the “thing” parameter is determined at that moment based on the value we pass to it.


Generic Types

So far, it has been really cool. We were able to write a function that can take an argument of any type. Now, what if we want to create a type that can hold any kind of value? For example, a slice of anything. Yes, generics can also be used inside type definitions. We can define this type as follows:

type Bunch[E any] []E

Here, we say that the type Bunch is a slice of a specific type named E. Now, this E is a generic that can be of any type. For example, Bunch[int] is a slice of type int. An example in code:

x := Bunch[int]{1, 2, 3}

We can also use this type in functions:

func PrintBunch[E any](b Bunch[E]) {
     for _,v := range b {
        fmt.Println(v)
     }
}

It can even have methods:

func (b Bunch[E]) Print() {
    for _,v := range b {
        fmt.Println(v)
     }
}

We can define certain constraints and conditions for generics. For example, we can specify that the generic only accepts stringer data types. Like in the following code:

type StringableBunch[E Stringer] []E

This feature is available in Go version 1.18 and later. (You can get the Go 1.18 version from this link. If for some reason you cannot install this version of Go, you can try it out in your browser from this link.

I hope my explanations have been helpful. You can find additional details in this link. If you found it useful and liked it, don’t forget to give it a like! You can also provide feedback and suggestions in the comments.