💡 Allow functions or data structures to take in several types

One big constraint of Go is that you have to specify a specific type as input to a function or data structure. For example, you can't create a Perl-style map { ... } function that takes both strings and ints - you have to define functions separately for each.

There are two main points to Go's generics implementation:

Type Parameters

A really simple example of Generics in action:

// Note square brackets used for the type parameter
func Print[T any](s []T) {
  for _, v := range s {
    fmt.Printf("%v ", v)
  }
}

func main() {
	// Explicitly specify the type T
  PrintSlice(int)([]int{1, 2, 3, 4, 5})
	// This also works due to type inference
	PrintSlice([]int{1, 2, 3, 4, 5}) 
}

[T any] is the type parameter list, and s []T is the actual parameter.

You don't need the int type declaration when calling PrintSlice as we can print all Go's types directly and the compiler can make the correct type inference.

For a more complex example, we can create a generic map function like in Perl:

func Map[K, V any](s []K, transform func(K) V) []V {
	rs := make([]V, len(s))
	for i, v := range s {
		rs[i] = transform(v)
	}
	return rs

}
func main() {
	stringArr := []string{"a", "b"}	
	dArr1 := Map(stringArr, func(s string) string { return s + s })
	fmt.Println(dArr1)
	
	intArr := []int{1, 2, 3}
	dArr2 := Map(intArr, func(s int) int { return s + s })
	fmt.Println(dArr2)
}

// [aa bb]
// [2 4 6]

Constraints

When performing actions on a generic type, a given operation might not be supported by all types.

func Smallest[T any](s []T) T {
	r := s[0]
	for _, v := range s[1:] {
		if v < r {
			r = v
		}
	}
	return r
}

func main() {
	arr := []int{7, 99, 4, 132, 2, 7}
	fmt.Println(Smallest(arr))
}

This returns an error when compiled: operator < not defined for T, as not all types have the < operator defined on them.

To solve this, we can define a superclass containing any types we want to support, and use this instead of any:

type SignedInteger interface {
  type int, int8, int16, int32, int64
}

func Smallest[T SignedInteger](s []T) T {
	// ...

Go also has some built in constraint types, such as comparable which are types that support == and !=. This avoids having to create an interface with a giant list of types as above! However, this doesn't yet appear to be implemented.