--- slug: generics-in-go-1.18 title: "Generics in Go 1.18" date: "2022-01-17T00:49:28-04:00" --- I spent some time trying the [generics types and generic functions][generics] in [Go 1.18][], and I like what they've got so far. Below is a [Go 1.17][] program which prints the results of the following functions: * `SumInts()`: calculate the the sum of of a slice of `int` values. * `SumFloats()`: calculate the the sum of of a slice of `float32` values. * `SumComplexes()`: calculate the the sum of of a slice of `complex64` values. ```go package main import "fmt" // Return sum of all values in slice of ints. func SumInts(m []int) int { var r int for _, v := range(m) { r += v } return r } // Return sum of all values in slice of float32s. func SumFloats(m []float32) float32 { var r float32 for _, v := range(m) { r += v } return r } // Return sum of all values in slice of complex64s. func SumComplexes(m []complex64) complex64 { var r complex64 for _, v := range(m) { r += v } return r } var ( // test integers ints = []int { 10, 20, 30 } // test floating point numbers floats = []float32 { 10.0, 20.0, 30.0 } // test complex numbers complexes = []complex64 { complex(10, 1), complex(20, 2), complex(30, 3) } ) func main() { // print sums fmt.Printf("ints = %d\n", SumInts(ints)) fmt.Printf("floats = %2.1f\n", SumFloats(floats)) fmt.Printf("complexes = %g\n", SumComplexes(complexes)) } ```   Here's the same program, written using [Go 1.18][] [generics][]: ```go package main import "fmt" // Return sum of all numeric values in slice. func Sum[V ~int|~float32|~complex64](vals []V) V { var r V for _, v := range(vals) { r += v } return r } var ( // test integers ints = []int { 10, 20, 30 } // test floating point numbers floats = []float32 { 10.0, 20.0, 30.0 } // test complex numbers complexes = []complex64 { complex(10, 1), complex(20, 2), complex(30, 3) } ) func main() { // print sums using generics w/explicit types fmt.Printf("ints = %d\n", Sum[int](ints)) fmt.Printf("floats = %2.1f\n", Sum[float32](floats)) fmt.Printf("complexes = %g\n", Sum[complex64](complexes)) } ```   You can use [type inference][] to drop the type parameters in many instances. For example, we can rewrite `main()` from the previous example like this: ```go func main() { // print sums using generics w/explicit types fmt.Printf("ints = %d\n", Sum(ints)) fmt.Printf("floats = %2.1f\n", Sum(floats)) fmt.Printf("complexes = %g\n", Sum(complexes)) } ```   [Generics][] can also be used in type definitions. Example: ```go package main import "fmt" // Fraction type Frac[T ~int|~int32|~int64] struct { num T // numerator den T // denominator } // Add two fractions. func (a Frac[T]) Add(b Frac[T]) Frac[T] { return Frac[T] { a.num + b.num, a.den * b.den } } // Multiple fractions. func (a Frac[T]) Mul(b Frac[T]) Frac[T] { return Frac[T] { a.num * b.num, a.den * b.den } } // Return inverse of fraction. func (a Frac[T]) Inverse() Frac[T] { return Frac[T] { a.den, a.num } } // Return string representation of fraction. func (a Frac[T]) String() string { return fmt.Sprintf("%d/%d", a.num, a.den) } func main() { // test fractions fracs = []Frac[int] { Frac[int] { 1, 2 }, Frac[int] { 3, 4 }, Frac[int] { 5, 6 }, } // print fractions for _, f := range(fracs) { fmt.Printf("%s => %s\n", f, f.Mul(f.Add(f.Inverse()))) } } ```   Interface type declarations can now be used to define the constraints that a matching type must satisfy. In addition to the ability to specify methods that a matching type must implement, a type constraint specified as an interface may also specify a union of terms indicating the set of matching types. Type union terms can be tilde-prefixed (example: `~int`), which indicates that the *underlying type* must match the given type. For example, the `Frac` type declaration from the previous example could be written like this instead: ```go // Integral number type. type integral interface { ~int | ~int32 | ~int64 } // Fraction type Frac[T integral] struct { num T // numerator den T // denominator } ```   There are two new predeclared identifiers: * `any`: An alias for `interface {}`. * `comparable`: Any type which can be compared for equality with `==` and `!=`. Useful for the parameterizing map keys. There is a new `constraints` package, which (not yet visible in the [online Go documentation][go-docs] as of this writing) that provides a couple of useful unions, but it's relatively anemic at the moment: ```bash $ go1.18beta1 doc -all constraints package constraints // import "constraints" Package constraints defines a set of useful constraints to be used with type parameters. TYPES type Complex interface { ~complex64 | ~complex128 } Complex is a constraint that permits any complex numeric type. If future releases of Go add new predeclared complex numeric types, this constraint will be modified to include them. type Float interface { ~float32 | ~float64 } Float is a constraint that permits any floating-point type. If future releases of Go add new predeclared floating-point types, this constraint will be modified to include them. type Integer interface { Signed | Unsigned } Integer is a constraint that permits any integer type. If future releases of Go add new predeclared integer types, this constraint will be modified to include them. type Ordered interface { Integer | Float | ~string } Ordered is a constraint that permits any ordered type: any type that supports the operators < <= >= >. If future releases of Go add new ordered types, this constraint will be modified to include them. type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } Signed is a constraint that permits any signed integer type. If future releases of Go add new predeclared signed integer types, this constraint will be modified to include them. type Unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } Unsigned is a constraint that permits any unsigned integer type. If future releases of Go add new predeclared unsigned integer types, this constraint will be modified to include them. ```   Using the `constraints` package, the `Frac` type from the previous example could be written like this: ```go import "constraints" // Fraction type Frac[T constraints.Signed] struct { num T // numerator den T // denominator } ```   And with `constraints`, the `Sum()` function from the first example could be defined like this: ```go // Numeric value. type Number interface { constraints.Integer | constraints.Float | constraints.Complex } // Return sum of all numeric values in slice. func Sum[V Number](vals []V) V { var r V for _, v := range(vals) { r += v } return r } ```   Other useful tidbits: * No [type erasure][]. * The standard library is still backwards compatible, so there is no need to rewrite your existing code. * There are [two new tutorials][] which explain generics and fuzzing. **Update (2021-01-19):** Minor wording changes, add information about tilde prefixes in type constraints. [go]: https://go.dev/ "Go programming language" [go 1.18]: https://tip.golang.org/doc/go1.18 "Go 1.18" [go 1.17]: https://go.dev/doc/go1.17 "Go 1.17" [generics]: https://en.wikipedia.org/wiki/Generic_programming "Generic programming" [type inference]: https://en.wikipedia.org/wiki/Type_inference "Automatic detection of variable types." [go-docs]: https://pkg.go.dev/ "Online Go package documentation." [type erasure]: https://en.wikipedia.org/wiki/Type_erasure "Type erasure." [two new tutorials]: https://go.dev/blog/tutorials-go1.18 "New Go tutorials which explain generics and fuzzing."