Dev /doc/tutoria

preface

Just two days ago, Go released Beta 1 version 1.18, which introduced the long-awaited introduction of generics. This article will give you a simple example of how to use Go generics.

Environmental requirements

  • Go 1.18 Beta 1 or later: To download Go 1.18, Go to Unstable Version at the bottom
  • Generic-friendly ides: The latest version of Goland has some support for generics

Non-generic functions

We now need a function to sum the values of map, but since we need to sum the values of int64 and float64, we have to write two summation functions. As follows:

package main

import (
   "fmt"
)

func main(a) {
   ints := map[string]int64{
      "first":  34."second": 12,
   }
   floats := map[string]float64{
      "first":  35.98."second": 26.99,
   }

   fmt.Printf("Non-generic summation: %v and %v\n", SumInts(ints), SumFloats(floats))
}

func SumInts(m map[string]int64) int64 {
   var s int64
   for _, v := range m {
      s += v
   }
   return s
}

func SumFloats(m map[string]float64) float64 {
   var s float64
   for _, v := range m {
      s += v
   }
   return s
}
Copy the code

Generic function

To support generic functions, we need to specify generic function parameters. The operations on generic parameters must be supported by all parameter types. For example, if your generic parameters contain string and int, you will not be able to use operations like string[1]. Here is the generic version of Sum() that supports int64 and float64:

func SumIntsOrFloats[K comparable.V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}
Copy the code
  • In this function you declare two type parameters, K and V, which are the key and value types of the map
  • The type parameter K is specified as the type constraint COMPARABLE. Comparable has been pre-declared by Go (available in the builtin. Go file), which means that any type that can use == and! = The type of comparison. Because map keys need to be comparable, the COMPARABLE type constraint needs to be used. (Map, slice, func are not comparable)
  • V type parameter is specified as a joint int64 and float64 type constraints, using | to said joint, said the value of the map must be int64 and float64 one of them.
  • Specify that the type of m is map[K]V. This constraints that the map key must be COMPARABLE, the value must be INT64 or FLOAT64, and that the return value must be of the same type as the map value.

We can use it like this:

fmt.Printf("Generic sum: %v and %v\n", SumIntsOrFloats[string.int64](ints), SumIntsOrFloats[string.float64](floats))
Copy the code

You can also ignore the type argument in this example, because the compiler can infer it completely:

fmt.Printf("Generic sum, parameter type inference: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
Copy the code

Type arguments can be useful, though, if you call a function that has no arguments.

Declare type constraints

You can define a type constraint as an interface so that you can reuse your type constraints and organize more complex code. Such as:

type Number interface {
    int64 | float64
}
Copy the code
  • You declare a type constraint called Number
  • Int64 and FLOAT64 are combined to encapsulate the type constraints in the int64 and float64 interfaces so that the constraints can be reused.

Here is the Sum() that constrains the interface version using the Number type:

func SumNumbers[K comparable.V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}
Copy the code
  • As you can see, just put V int64 | float64 with V Number

It can be used like this:

fmt.Printf("Generic sum, type constrained interface: %v and %v\n", SumNumbers(ints), SumNumbers(floats))
Copy the code

any

Any is just another name for interface{}. It behaves exactly like interface{}, but it looks more comfortable and you can leave interface{} behind. For example, write a generic ForEach() with any:

func ForEach[T any](list []T, action func(T)) {
   for _, item := range list {
      action(item)
   }
}
Copy the code
  • ForEach() above uses any with type arguments, allowing ForEach() to accept arguments of any type.

It can be used like this:

ForEach([]string{"Hello,"."Generic!"}, func(s string) {
   fmt.Printf(s)
})
Copy the code

If no type parameters are used, only casts can be used:

func ForEachWithInterface(list []any, action func(any)) {
   for _, item := range list {
      action(item)
   }
}
Copy the code
ForEachWithInterface([]any{"Hello,"."Generic!"}, func(s any) {
   fmt.Printf(s.(string))})Copy the code
  • The above code enforces an interface{} argument to a string.

All the code

package main

import (
   "fmt"
)

type Number interface {
   int64 | float64
}

func main(a) {
   ints := map[string]int64{
      "first":  34."second": 12,
   }
   floats := map[string]float64{
      "first":  35.98."second": 26.99,
   }

   fmt.Printf("Non-generic summation: %v and %v\n", SumInts(ints), SumFloats(floats))

   fmt.Printf("Generic sum: %v and %v\n", SumIntsOrFloats[string.int64](ints), SumIntsOrFloats[string.float64](floats))

   fmt.Printf("Generic sum, parameter type inference: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))

   fmt.Printf("Generic sum, type constrained interface: %v and %v\n", SumNumbers(ints), SumNumbers(floats))

   ForEach([]string{"Hello,"."Generic!"}, func(s string) {
      fmt.Printf(s)
   })

   fmt.Println()

   ForEachWithInterface([]interface{} {"Hello,"."Generic!"}, func(s interface{}) {
      fmt.Printf(s.(string))})}func SumInts(m map[string]int64) int64 {
   var s int64
   for _, v := range m {
      s += v
   }
   return s
}

func SumFloats(m map[string]float64) float64 {
   var s float64
   for _, v := range m {
      s += v
   }
   return s
}

func SumIntsOrFloats[K comparable.V Number](m map[K]V) V {
   var s V
   for _, v := range m {
      s += v
   }
   return s
}

func SumNumbers[K comparable.V Number](m map[K]V) V {
   var s V
   for _, v := range m {
      s += v
   }
   return s
}

func ForEach[T any](list []T, action func(T)) {
   for _, item := range list {
      action(item)
   }
}

func ForEachWithInterface(list []any, action func(any)) {
   for _, item := range list {
      action(item)
   }
}
Copy the code