preface

This week, Go officially released the Go 1.18 Beta 1 version, which officially supports generics. As the biggest functional change in the 12 years since the birth of Go, the official release of a very detailed Go generics basic tutorial, easy to understand.

I have made some optimization on the expression of Go official tutorial on the basis of translation for readers.

The tutorial content

This tutorial focuses on the basics of Go generics. With generics, you can declare and use generic functions, allowing different types of arguments to be used as arguments when calling functions.

In this tutorial, we will declare two simple non-generic functions and then implement the logic of both functions in a generic function.

The following parts will be used to explain:

  1. Create a directory for your code

  2. Implement non-generic functions

  3. Implement a generic function to handle different types

  4. Removes type arguments when a generic function is called

  5. Declare type Constraints

Note: For other tutorials on Go, please refer to go.dev/doc/tutoria… .

Note: You can use the Go dev Branch mode of Go Playground to write and run your generic code at go.dev/play/? V =got… .

The preparatory work

  • Install Go 1.18 Beta 1 or later. Please refer to the introduction below for installation instructions.

  • There is a code editing tool. Any text editor will do.

  • There is a command line terminal. Go can run on Linux, any command-line terminal on the Mac, or on Windows PowerShell or CMD.

Install and use the beta

This tutorial requires the use of generics functionality from Go 1.18 Beta 1. Use the following steps to install the beta

  1. Use the following command to install the beta version

    $go install golang.org/dl/go1.18beta1@latestCopy the code
  2. Run the following command to download the update

    $ go1.18beta1 download
    Copy the code
  3. Use the beta version of go, not the Release version of Go

    You can do this by using the go1.18beta1 command directly or by giving go1.18beta1 a simple alias

    • Run the go1.18beta1 command directly

      $ go1.18beta1 version
      Copy the code
    • Give the go1.18beta1 command an alias

      $alias go=go1.18beta1 $go versionCopy the code

    The following tutorials assume that you have alias go for the go1.18beta1 command.

Create a directory for your code

Start by creating a directory for your code.

  1. Open a command line terminal and switch to your home directory

    • Run the following command on Linux or Mac (on Linux or Mac, simply run CD to access the home directory)

      cd
      Copy the code
    • Run the following command on Windows

      C:\> cd %HOMEPATH%
      Copy the code
  2. At the command line terminal, create a directory named Generics

    $ mkdir generics
    $ cd generics
    Copy the code
  3. Create a Go Module

    Run the go mod init command to set the Module path for your project

    $ go mod init example/generics
    Copy the code

    Note: For production code, you can specify the module path depending on your project. For more information, see go.dev/doc/modules… .

Next, let’s write some simple code using Map.

Implement non-generic functions

In this step, you implement two functions, each of which adds up all the values corresponding to

in the map and returns the sum.
,>

You need to declare two functions because you are dealing with two different types of map, one of which stores value of type INT64 and one of which stores value of type float64.

Code implementation

  1. Open your code editor and create a file main.go in the Generics directory where your code will be implemented.

  2. Go to main.go and write the package declaration at the top of the file

    package main
    Copy the code

    A stand-alone executable is always declared in Package Main, unlike a library.

  3. Under the package declaration, write the following code

    // SumInts adds together the values of m.
    func SumInts(m map[string]int64) int64 {
        var s int64
        for _, v := range m {
            s += v
        }
        return s
    }
    
    // SumFloats adds together the values of m.
    func SumFloats(m map[string]float64) float64 {
        var s float64
        for _, v := range m {
            s += v
        }
        return s
    }
    Copy the code

    In the above code, we define two functions to calculate the sum of values in the map

    • SumInts calculates the sum of int64 types for value

    • SumFloats Calculates the sum of float64 values

  4. Under the package main declaration of main.go, implement the main function that initializes the two maps and passes them as arguments to the two functions we implemented.

    func main() { // Initialize a map for the integer values ints := map[string]int64{ "first": 34, "second": Floats := map[string]float64{" float ": floats () {" float ": floats ();} // Initialize a map for the float values floats := map[string]float64{" float ": floats (); Printf(" Non-generic Sums: %v and %v\n", SumInts(ints), SumFloats(floats))}Copy the code

    In this code, we do the following things

    • Initialize two maps, each with two records

    • Call SumInts and SumFloats to calculate the sum of the values of the two maps

    • Print the result

  5. Under the package main of main.go, add import FMT, which is required by the print function called in the above code.

  6. Save the main. Go.

Code to run

In the main.go directory, run the following command

$go run. Non-generic Sums: 46 and 62.97Copy the code

With generics, we only need to implement one function to calculate the sum of the values of two different types of maps. Next, we will show how to implement this generic function.

Implement a generic function to handle different types

In this section, we will implement a generic function that accepts both a map of value int and a map of value float, so that we do not have to implement separate functions for each type of map.

There are two prerequisites for a function to support this generic behavior:

  1. For functions, there needs to be a way to declare what types of arguments the function supports

  2. For function callers, there needs to be a way to specify whether the map passed to the function is an int or a float map

In order to meet the above prerequisites:

  1. When declaring a function, you need to add function parameters as normal functions do, and also declare type parameters. These type parameters allow functions to implement generic behavior, allowing functions to handle different types of parameters.

  2. At function call time, in addition to passing arguments like normal function calls, you also need to specify the type arguments corresponding to the type arguments of the generic function.

Each type parameter has a Type constraint, just like the meta type of the type parameter. Each type constraint specifies the type arguments allowed for that type parameter when the function is called.

Although the type restriction of a type parameter is a collection of types, at compile time, the type parameter represents only one concrete type, the type argument that the function caller actually uses. If the type of the type argument does not meet the type restriction for the type parameter, compilation fails.

Remember: A type parameter must support all operations done on that type in the code. For example, if your function code tries to perform a string operation on a type parameter, such as a value indexed by index, but the type restriction for that type parameter includes a numeric type, the code will fail to compile.

In the following code, we will use type restrictions to allow maps with values of type int and type float to be used as input arguments to the function.

Code implementation

  1. After the SumInts and SumFloats implemented above, add the following functions

    // SumIntsOrFloats sums the values of map m. It supports both int64 and float64
    // as types for map values.
    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 code, we do the following:

    • Floats declare a function SumIntsOrFloats. It takes 2 type parameters K and V(inside []). A function parameter m has type map[K]V and returns a function whose return type is V.

    • The type constraint for the type parameter K is COMPARABLE. Comparable is pre-declared in Go. It can accept anything that can do == and! = Type of operation. The key of a map in Go must be comparable, so it is necessary to restrict the use of COMPARABLE for the type parameter K to ensure that the caller is using a valid type as the map key.

    • Type parameters V type restrictions are int64 and float64, | said set, with the is either int64 and float64 can satisfy the type restrictions, can be used as a function of the caller to use type arguments.

    • The function argument m is of type map[K]V. We know that map[K]V is a legal map type because K is a COMPARABLE type. If we do not declare K comparable, the compiler will reject a reference to map[K]V.

  2. Add the following code to the existing code in main.go

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

    In this code:

    • The generic function defined above is called, passing two types of Map as arguments to the function.

    • A function call specifies a type argument (the type name in square brackets []) to replace the type argument of the called function.

      In the following sections, you’ll often see type arguments omitted when calling functions, because Go can usually (but not always) infer type arguments from your code.

    • Prints the return value of the function.

Code to run

In the main.go directory, run the following command:

$go run. Non-generic Sums: 46 and 62.97 Generic Sums: 46 and 62.97Copy the code

The compiler automatically replaces the type arguments in the function with the type arguments specified in the function call. In many scenarios, we can ignore these type arguments because the compiler can deduce them automatically.

Removes type arguments when a generic function is called

In this section, we’ll add a modified version of the generic function call to make it more concise by removing the type arguments from the function call.

The reason we can remove type arguments in a function call is because the compiler can deduce them automatically from the type of argument passed in the function call.

Note: Automatic derivation of type arguments is not always possible. For example, if you call a generic function that has no parameters and does not need to pass arguments, then the compiler cannot automatically derive from the arguments and needs to display the specified type arguments in square brackets [] when the function is called.

Code implementation

  • Add the following code to the existing code in main.go

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))
    Copy the code

    In this code, we call the generic function, ignore the type arguments, and hand it over to the compiler for automatic type derivation.

Code to run

In the main.go directory, run the following command:

$go run. Non-generic Sums: 46 and 62.97 Generic Sums: 46 and 62.97 Generic Sums, type parameters: 46 and 62.97Copy the code

Next, we will simplify generic functions further. We can make the union of int and float a reusable type constraint.

Declare type Constraints

In the last section, we will define type limits in generic functions as interfaces, so that type limits can be reused in many places. Declaring type restrictions can help simplify code, especially in scenarios where type restrictions are complex.

We can declare a Type constraint for the interface type. Such a type restriction allows any type that implements the interface to be a type argument to a generic function. For example, if you declare a type limiting interface with three methods, and then apply the type limiting interface to a generic function, the function call must have type arguments that implement all the methods in the interface.

Type-limiting interfaces can also refer to specific types, which you’ll see in action below.

Code implementation

  1. Above the main function and below the import statement, add the following code to declare a type restriction

    type Number interface {
        int64 | float64
    }
    Copy the code

    In this code, we are

    • Declares an interface type named Number for type restriction

    • In the interface definition, we declare the union of INT64 and float64

    We originally to function declarations in int64 and float64 and set into a new type of limit interface Number, when we need to limit int64 or float64 for type parameters, you can use the Number limit to replace the type int64 | float64 writing.

  2. Add a new SumNumbers generic function under the existing one

    // SumNumbers sums the values of map m. Its supports both integers
    // and floats as map values.
    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

    In this code

    • We define a new generic function, function logic and the generic function defined beforeSumIntsOrFloatsExactly the same, except for the type parameter V, we use Number as the type constraint. As before, we use type parameters for function parameters and function return types.
  3. Add the following code to the existing code in main.go

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
    Copy the code

    In this code

    • We call SumNumbers on both maps, printing the return value of each function call.

      As above, in this generic function call, we ignore the type arguments (the type name in square brackets []) from which the Go compiler does automatic type inference.

Code to run

In the main.go directory, run the following command:

$go run. Non-generic Sums: 46 and 62.97 Generic Sums: 46 and 62.97 Generic Sums, type parameters: 61 and 62.97 Generic Sums with Constraint: 61 and 62.97Copy the code

conclusion

So far, we’ve covered the basics of Go generics.

If you want to continue experimenting, you can extend the Number interface to support more Number types.

Recommended topics to follow:

  • Go Tour: Go. Dev/Tour/welcom… , very good Go basic guide, step by step to teach you to get started Go.

  • Effective Go: go.dev/doc/effecti… And How to Write Go code: go.dev/doc/code Find best practices for writing Go code.

The complete code

// example6.go package main import "fmt" type Number interface { int64 | float64 } func main() { // Initialize a map for  the integer values ints := map[string]int64{ "first": 34, "second": Floats := map[string]float64{" float ": floats () {" float ": floats ();} // Initialize a map for the float values floats := map[string]float64{" float ": floats (); } FMt. Printf(" Non-generic Sums: %v and %v\n", SumInts(ints), SumFloats(floats)) FMt. Printf("Generic Sums: %v and %v\n", SumInts(ints), SumFloats(floats) %v and %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats)) fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats)) fmt.Printf("Generic Sums with Constraint: %v and %v\n", SumNumbers(ints), SumNumbers(floats)) } // SumInts adds together the values of m. func SumInts(m map[string]int64) int64 { var s int64 for _, v := range m { s += v } return s } // SumFloats adds together the values of m. func SumFloats(m map[string]float64) float64 { var s float64 for _, v := range m { s += v } return s } // SumIntsOrFloats sums the values of map m. It supports both floats and integers // as map values. func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V { var s V for _, v := range m { s += v } return s } // SumNumbers sums the values of map m. Its supports both integers // and floats as map values. 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

Open source address

Documentation and code: github.com/jincheng9/g…

We also welcome you to pay attention to the public account: Coding to learn more about Go, micro services and cloud native architecture.

References

  • Go. Dev/doc/tutoria…