This tutorial introduces the basics of generics in Go. With generics, you candeclare and use functions or types that are written to work with any of a setof types provided by calling code.
In this tutorial, you’ll declare two simple non-generic functions, then capturethe same logic in a single generic function.
You’ll progress through the following sections:
- Create a folder for your code.
- Add non-generic functions.
- Add a generic function to handle multiple types.
- Remove type arguments when calling the generic function.
- Declare a type constraint.
Note: For other tutorials, see Tutorials.
Note: If you prefer, you can usethe Go playground in “Go dev branch” modeto edit and run your program instead.
Prerequisites
- An installation of Go 1.18 or later. For installation instructions, seeInstalling Go.
- A tool to edit your code. Any text editor you have will work fine.
- A command terminal. Go works well using any terminal on Linux and Mac,and on PowerShell or cmd in Windows.
Create a folder for your code
To begin, create a folder for the code you’ll write.
Open a command prompt and change to your home directory.
On Linux or Mac:
$ cd
On Windows:
C:\> cd %HOMEPATH%
The rest of the tutorial will show a $ as the prompt. The commands you usewill work on Windows too.
From the command prompt, create a directory for your code called generics.
$ mkdir generics$ cd generics
Create a module to hold your code.
Run the
go mod init
command, giving it your new code’s module path.$ go mod init example/genericsgo: creating new go.mod: module example/generics
Note: For production code, you’d specify a module path that’s more specificto your own needs. For more, be sure to seeManaging dependencies.
Next, you’ll add some simple code to work with maps.
Add non-generic functions
In this step, you’ll add two functions that each add together the values of amap and return the total.
You’re declaring two functions instead of one because you’re working with twodifferent types of maps: one that stores int64
values, and one that stores float64
values.
Write the code
Using your text editor, create a file called main.go in the genericsdirectory. You’ll write your Go code in this file.
See AlsoDocumentation - GenericsInto main.go, at the top of the file, paste the following packagedeclaration.
package main
A standalone program (as opposed to a library) is always in package
main
.Beneath the package declaration, paste the following two functiondeclarations.
// 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}
In this code, you:
- Declare two functions to add together the values of a map and returnthe sum.
SumFloats
takes a map ofstring
tofloat64
values.SumInts
takes a map ofstring
toint64
values.
- Declare two functions to add together the values of a map and returnthe sum.
At the top of main.go, beneath the package declaration, paste the following
main
function to initialize the two maps and use them as arguments whencalling the functions you declared in the preceding step.func main() { // Initialize a map for the integer values ints := map[string]int64{ "first": 34, "second": 12, } // Initialize a map for the float values floats := map[string]float64{ "first": 35.98, "second": 26.99, } fmt.Printf("Non-Generic Sums: %v and %v\n", SumInts(ints), SumFloats(floats))}
In this code, you:
- Initialize a map of
float64
values and a map ofint64
values, eachwith two entries. - Call the two functions you declared earlier to find the sum of eachmap’s values.
- Print the result.
- Initialize a map of
Near the top of main.go, just beneath the package declaration, import thepackage you’ll need to support the code you’ve just written.
The first lines of code should look like this:
package mainimport "fmt"
Save main.go.
Run the code
From the command line in the directory containing main.go, run the code.
$ go run .Non-Generic Sums: 46 and 62.97
With generics, you can write one function here instead of two. Next, you’lladd a single generic function for maps containing either integer or float values.
Add a generic function to handle multiple types
In this section, you’ll add a single generic function that can receive a mapcontaining either integer or float values, effectively replacing the twofunctions you just wrote with a single function.
To support values of either type, that single function will need a way todeclare what types it supports. Calling code, on the other hand, will need away to specify whether it is calling with an integer or float map.
To support this, you’ll write a function that declares type parameters inaddition to its ordinary function parameters. These type parameters make thefunction generic, enabling it to work with arguments of different types. You’llcall the function with type arguments and ordinary function arguments.
Each type parameter has a type constraint that acts as a kind of meta-typefor the type parameter. Each type constraint specifies the permissible typearguments that calling code can use for the respective type parameter.
While a type parameter’s constraint typically represents a set of types, atcompile time the type parameter stands for a single type – the type providedas a type argument by the calling code. If the type argument’s type isn’tallowed by the type parameter’s constraint, the code won’t compile.
Keep in mind that a type parameter must support all the operations the genericcode is performing on it. For example, if your function’s code were to try toperform string
operations (such as indexing) on a type parameter whoseconstraint included numeric types, the code wouldn’t compile.
In the code you’re about to write, you’ll use a constraint that allows eitherinteger or float types.
Write the code
Beneath the two functions you added previously, paste the following genericfunction.
// 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}
In this code, you:
- Declare a
SumIntsOrFloats
function with two type parameters (insidethe square brackets),K
andV
, and one argument that uses the typeparameters,m
of typemap[K]V
. The function returns a value oftypeV
. - Specify for the
K
type parameter the type constraintcomparable
.Intended specifically for cases like these, thecomparable
constraintis predeclared in Go. It allows any type whose values may be used as anoperand of the comparison operators==
and!=
. Go requires that mapkeys be comparable. So declaringK
ascomparable
is necessary so youcan useK
as the key in the map variable. It also ensures that callingcode uses an allowable type for map keys. - Specify for the
V
type parameter a constraint that is a union of twotypes:int64
andfloat64
. Using|
specifies a union of the twotypes, meaning that this constraint allows either type. Either typewill be permitted by the compiler as an argument in the calling code. - Specify that the
m
argument is of typemap[K]V
, whereK
andV
are the types already specified for the type parameters. Note that weknowmap[K]V
is a valid map type becauseK
is a comparable type. Ifwe hadn’t declaredK
comparable, the compiler would reject thereference tomap[K]V
.
- Declare a
In main.go, beneath the code you already have, paste the following code.
fmt.Printf("Generic Sums: %v and %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats))
In this code, you:
Call the generic function you just declared, passing each of the mapsyou created.
Specify type arguments – the type names in square brackets – to beclear about the types that should replace type parameters in thefunction you’re calling.
As you’ll see in the next section, you can often omit the typearguments in the function call. Go can often infer them from your code.
Print the sums returned by the function.
Run the code
From the command line in the directory containing main.go, run the code.
$ go run .Non-Generic Sums: 46 and 62.97Generic Sums: 46 and 62.97
To run your code, in each call the compiler replaced the type parameters withthe concrete types specified in that call.
In calling the generic function you wrote, you specified type arguments thattold the compiler what types to use in place of the function’s type parameters.As you’ll see in the next section, in many cases you can omit these typearguments because the compiler can infer them.
Remove type arguments when calling the generic function
In this section, you’ll add a modified version of the generic function call,making a small change to simplify the calling code. You’ll remove the typearguments, which aren’t needed in this case.
You can omit type arguments in calling code when the Go compiler can infer thetypes you want to use. The compiler infers type arguments from the types offunction arguments.
Note that this isn’t always possible. For example, if you needed to call ageneric function that had no arguments, you would need to include the typearguments in the function call.
Write the code
In main.go, beneath the code you already have, paste the following code.
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
In this code, you:
- Call the generic function, omitting the type arguments.
Run the code
From the command line in the directory containing main.go, run the code.
$ go run .Non-Generic Sums: 46 and 62.97Generic Sums: 46 and 62.97Generic Sums, type parameters inferred: 46 and 62.97
Next, you’ll further simplify the function by capturing the union of integersand floats into a type constraint you can reuse, such as from other code.
Declare a type constraint
In this last section, you’ll move the constraint you defined earlier into itsown interface so that you can reuse it in multiple places. Declaringconstraints in this way helps streamline code, such as when a constraint ismore complex.
You declare a type constraint as an interface. The constraint allows anytype implementing the interface. For example, if you declare a type constraintinterface with three methods, then use it with a type parameter in a genericfunction, type arguments used to call the function must have all of thosemethods.
Constraint interfaces can also refer to specific types, as you’ll see in thissection.
Write the code
Just above
main
, immediately after the import statements, paste thefollowing code to declare a type constraint.type Number interface { int64 | float64}
In this code, you:
Declare the
Number
interface type to use as a type constraint.Declare a union of
int64
andfloat64
inside the interface.Essentially, you’re moving the union from the function declarationinto a new type constraint. That way, when you want to constrain a typeparameter to either
int64
orfloat64
, you can use thisNumber
type constraint instead of writing outint64 | float64
.
Beneath the functions you already have, paste the following generic
SumNumbers
function.// SumNumbers sums the values of map m. It 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}
In this code, you:
- Declare a generic function with the same logic as the generic functionyou declared previously, but with the new interface type instead of theunion as the type constraint. As before, you use the type parametersfor the argument and return types.
In main.go, beneath the code you already have, paste the following code.
fmt.Printf("Generic Sums with Constraint: %v and %v\n", SumNumbers(ints), SumNumbers(floats))
In this code, you:
Call
SumNumbers
with each map, printing the sum from the values ofeach.As in the preceding section, you omit the type arguments (the typenames in square brackets) in calls to the generic function. The Gocompiler can infer the type argument from other arguments.
Run the code
From the command line in the directory containing main.go, run the code.
$ go run .Non-Generic Sums: 46 and 62.97Generic Sums: 46 and 62.97Generic Sums, type parameters inferred: 46 and 62.97Generic Sums with Constraint: 46 and 62.97
Conclusion
Nicely done! You’ve just introduced yourself to generics in Go.
Suggested next topics:
- The Go Tour is a great step-by-stepintroduction to Go fundamentals.
- You’ll find useful Go best practices described inEffective Go andHow to write Go code.
Completed code
You can run this program in theGo playground. On theplayground simply click the Run button.
package mainimport "fmt"type Number interface { int64 | float64}func main() { // Initialize a map for the integer values ints := map[string]int64{ "first": 34, "second": 12, } // Initialize a map for the float values floats := map[string]float64{ "first": 35.98, "second": 26.99, } fmt.Printf("Non-Generic Sums: %v and %v\n", SumInts(ints), SumFloats(floats)) fmt.Printf("Generic Sums: %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}