Thomas Chavakis

Go - Generics

Abstract

In Go 1.18 which released on the 15th of March 2022 [5] added generics to the language, a long awaited feature in the Go Community.

The following proposal by Ian Lance Taylor is the design for adding generic programming using type parameters to the Go language.

This design has been proposed and accepted. This article presents basic functionality and examples of the Generics.

Type parameters

With generics, you can declare and use functions or types that are written to work with any of a set of types provided by calling code[1].

Generic code is written using abstract data types that we call type parameters. When running the generic code, the type parameters are replaced by type arguments.[4]

Functions and types are now permitted to have type parameters. A type parameter list looks like an ordinary parameter list, except that it uses square brackets instead of parentheses. [2]

Just as regular parameters have types, type parameters have meta-types, also known as constraints [4].

Function Min without Generics

func NormalMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

Function Min with Generics

func GenericsMin[T ~int32](a T, b T) T {
    if a < b {
        return a
    }
    return b
}

Type Constraint

Create a type interface to use as a type constraint. In this example as minType we can use a float64 or an integer.

Notes:

  • The vertical bar expresses a union of types. The type set of a union element is the union of the type sets of each element in the sequence.
  • ~T means the set of all types whose underlying type is T, so the ~float64 means the set of all types whose underlying type is float64. This includes the type float64 itself as well as all types declared with definitions such as type MyFloat64 float64.
  • ~T is not permitted if T is a type parameter or if T is an interface type. The related error is invalid use of ~ (interface{} is an interface)
type minTypes interface {
    ~float64 | int
}

func GenericsMinMinTypes[T minTypes](a T, b T) T {
    if a < b {
        return a
    }
    return b
}

How to use each function

func main() {
    fmt.Println(NormalMin(1, 2))
    fmt.Println(GenericsMin[int32](1, 2))
    fmt.Println(GenericsMinMinTypes(0.1, 0.01))
}

any

By declaring the token any you allow any type. Behind the scene any is an interface{}

func printAnyTypeArray[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}
func main(){
    printAnyTypeArray[int]([]int{1,2,3})
    printAnyTypeArray[float64]([]float64{0.1,0.01})
    printAnyTypeArray[string]([]string{"a","b"})
}

When I should not use generics

  • When a simple interface can be used instead
  • When the implementation is different for each type

References

  1. generics tutorial
  2. intro generics
  3. Generic Go to Go
  4. Generics Proposal
  5. Generics Release
  6. Tutorial - Golang Dojo