blob: ef99dbda7a4859c9d5537ba3228eb094af0f8406 [file] [log] [blame] [view]
# Type Parameters Proposal
Ian Lance Taylor\
Robert Griesemer\
August 20, 2021
## Status
This is the design for adding generic programming using type
parameters to the Go language.
This design has been [proposed and
accepted](https://golang.org/issue/43651) as a future language change.
We currently expect that this change will be available in the Go 1.18
release in early 2022.
## Abstract
We suggest extending the Go language to add optional type parameters
to type and function declarations.
Type parameters are constrained by interface types.
Interface types, when used as type constraints, support embedding
additional elements that may be used to limit the set of types that
satisfy the constraint.
Parameterized types and functions may use operators with type
parameters, but only when permitted by all types that satisfy the
parameter's constraint.
Type inference via a unification algorithm permits omitting type
arguments from function calls in many cases.
The design is fully backward compatible with Go 1.
## How to read this proposal
This document is long.
Here is some guidance on how to read it.
* We start with a high level overview, describing the concepts very
briefly.
* We then explain the full design starting from scratch, introducing
the details as we need them, with simple examples.
* After the design is completely described, we discuss implementation,
some issues with the design, and a comparison with other approaches
to generics.
* We then present several complete examples of how this design would
be used in practice.
* Following the examples some minor details are discussed in an
appendix.
## Very high level overview
This section explains the changes suggested by the design very
briefly.
This section is intended for people who are already familiar with how
generics would work in a language like Go.
These concepts will be explained in detail in the following sections.
* Functions can have an additional type parameter list that uses
square brackets but otherwise looks like an ordinary parameter list:
`func F[T any](p T) { ... }`.
* These type parameters can be used by the regular parameters and in
the function body.
* Types can also have a type parameter list: `type M[T any] []T`.
* Each type parameter has a type constraint, just as each ordinary
parameter has a type: `func F[T Constraint](p T) { ... }`.
* Type constraints are interface types.
* The new predeclared name `any` is a type constraint that permits any
type.
* Interface types used as type constraints can embed additional
elements to restrict the set of type arguments that satisfy the
contraint:
* an arbitrary type `T` restricts to that type
* an approximation element `~T` restricts to all types whose
underlying type is `T`
* a union element `T1 | T2 | ...` restricts to any of the listed
elements
* Generic functions may only use operations supported by all the types
permitted by the constraint.
* Using a generic function or type requires passing type arguments.
* Type inference permits omitting the type arguments of a function
call in common cases.
In the following sections we work through each of these language
changes in great detail.
You may prefer to skip ahead to the [examples](#Examples) to see what
generic code written to this design will look like in practice.
## Background
There have been many [requests to add additional support for generic
programming](https://github.com/golang/go/wiki/ExperienceReports#generics)
in Go.
There has been extensive discussion on
[the issue tracker](https://golang.org/issue/15292) and on
[a living document](https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4/view).
This design suggests extending the Go language to add a form of
parametric polymorphism, where the type parameters are bounded not by
a declared subtyping relationship (as in some object oriented
languages) but by explicitly defined structural constraints.
This version of the design has many similarities to a design draft
presented on July 31, 2019, but contracts have been removed and
replaced by interface types, and the syntax has changed.
There have been several proposals for adding type parameters, which
can be found through the links above.
Many of the ideas presented here have appeared before.
The main new features described here are the syntax and the careful
examination of interface types as constraints.
This design does not support template metaprogramming or any other
form of compile time programming.
As the term _generic_ is widely used in the Go community, we will
use it below as a shorthand to mean a function or type that takes type
parameters.
Don't confuse the term generic as used in this design with the same
term in other languages like C++, C#, Java, or Rust; they have
similarities but are not the same.
## Design
We will describe the complete design in stages based on simple
examples.
### Type parameters
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_.
Here is a function that prints out each element of a slice, where the
element type of the slice, here called `T`, is unknown.
This is a trivial example of the kind of function we want to permit in
order to support generic programming.
(Later we'll also discuss [generic types](#Generic-types)).
```
// Print prints the elements of a slice.
// It should be possible to call this with any slice value.
func Print(s []T) { // Just an example, not the suggested syntax.
for _, v := range s {
fmt.Println(v)
}
}
```
With this approach, the first decision to make is: how should the type
parameter `T` be declared?
In a language like Go, we expect every identifier to be declared in
some way.
Here we make a design decision: type parameters are similar to
ordinary non-type function parameters, and as such should be listed
along with other parameters.
However, type parameters are not the same as non-type parameters, so
although they appear in the list of parameters we want to distinguish
them.
That leads to our next design decision: we define an additional
optional parameter list describing type parameters.
This type parameter list appears before the regular parameters.
To distinguish the type parameter list from the regular parameter
list, the type parameter list uses square brackets rather than
parentheses.
Just as regular parameters have types, type parameters have
meta-types, also known as constraints.
We will discuss the details of constraints later; for now, we will
just note that `any` is a valid constraint, meaning that any type is
permitted.
```
// Print prints the elements of any slice.
// Print has a type parameter T and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print[T any](s []T) {
// same as above
}
```
This says that within the function `Print` the identifier `T` is a
type parameter, a type that is currently unknown but that will be
known when the function is called.
The `any` means that `T` can be any type at all.
As seen above, the type parameter may be used as a type when
describing the types of the ordinary non-type parameters.
It may also be used as a type within the body of the function.
Unlike regular parameter lists, in type parameter lists names are
required for the type parameters.
This avoids a syntactic ambiguity, and, as it happens, there is no
reason to ever omit the type parameter names.
Since `Print` has a type parameter, any call of `Print` must provide a
type argument.
Later we will see how this type argument can usually be deduced from
the non-type argument, by using [type inference](#Type-inference).
For now, we'll pass the type argument explicitly.
Type arguments are passed much like type parameters are declared: as a
separate list of arguments.
As with the type parameter list, the list of type arguments uses
square brackets.
```
// Call Print with a []int.
// Print has a type parameter T, and we want to pass a []int,
// so we pass a type argument of int by writing Print[int].
// The function Print[int] expects a []int as an argument.
Print[int]([]int{1, 2, 3})
// This will print:
// 1
// 2
// 3
```
### Constraints
Let's make our example slightly more complicated.
Let's turn it into a function that converts a slice of any type into a
`[]string` by calling a `String` method on each element.
```
// This function is INVALID.
func Stringify[T any](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String()) // INVALID
}
return ret
}
```
This might seem OK at first glance, but in this example `v` has type
`T`, and `T` can be any type.
This means that `T` need not have a `String` method.
So the call to `v.String()` is invalid.
Naturally, the same issue arises in other languages that support
generic programming.
In C++, for example, a generic function (in C++ terms, a function
template) can call any method on a value of generic type.
That is, in the C++ approach, calling `v.String()` is fine.
If the function is called with a type argument that does not have a
`String` method, the error is reported when compiling the call to
`v.String` with that type argument.
These errors can be lengthy, as there may be several layers of generic
function calls before the error occurs, all of which must be reported
to understand what went wrong.
The C++ approach would be a poor choice for Go.
One reason is the style of the language.
In Go we don't refer to names, such as, in this case, `String`, and
hope that they exist.
Go resolves all names to their declarations when they are seen.
Another reason is that Go is designed to support programming at
scale.
We must consider the case in which the generic function definition
(`Stringify`, above) and the call to the generic function (not shown,
but perhaps in some other package) are far apart.
In general, all generic code expects the type arguments to meet
certain requirements.
We refer to these requirements as _constraints_ (other languages have
similar ideas known as type bounds or trait bounds or concepts).
In this case, the constraint is pretty obvious: the type has to have a
`String() string` method.
In other cases it may be much less obvious.
We don't want to derive the constraints from whatever `Stringify`
happens to do (in this case, call the `String` method).
If we did, a minor change to `Stringify` might change the
constraints.
That would mean that a minor change could cause code far away, that
calls the function, to unexpectedly break.
It's fine for `Stringify` to deliberately change its constraints, and
force callers to change.
What we want to avoid is `Stringify` changing its constraints
accidentally.
This means that the constraints must set limits on both the type
arguments passed by the caller and the code in the generic function.
The caller may only pass type arguments that satisfy the constraints.
The generic function may only use those values in ways that are
permitted by the constraints.
This is an important rule that we believe should apply to any attempt
to define generic programming in Go: generic code can only use
operations that its type arguments are known to implement.
### Operations permitted for any type
Before we discuss constraints further, let's briefly note what happens
when the constraint is `any`.
If a generic function uses the `any` constraint for a type parameter,
as is the case for the `Print` method above, then any type argument is
permitted for that parameter.
The only operations that the generic function can use with values of
that type parameter are those operations that are permitted for values
of any type.
In the example above, the `Print` function declares a variable `v`
whose type is the type parameter `T`, and it passes that variable to a
function.
The operations permitted for any type are:
* declare variables of those types
* assign other values of the same type to those variables
* pass those variables to functions or return them from functions
* take the address of those variables
* convert or assign values of those types to the type `interface{}`
* convert a value of type `T` to type `T` (permitted but useless)
* use a type assertion to convert an interface value to the type
* use the type as a case in a type switch
* define and use composite types that use those types, such as a slice
of that type
* pass the type to some predeclared functions such as `new`
It's possible that future language changes will add other such
operations, though none are currently anticipated.
### Defining constraints
Go already has a construct that is close to what we need for a
constraint: an interface type.
An interface type is a set of methods.
The only values that can be assigned to a variable of interface type
are those whose types implement the same methods.
The only operations that can be done with a value of interface type,
other than operations permitted for any type, are to call the
methods.
Calling a generic function with a type argument is similar to
assigning to a variable of interface type: the type argument must
implement the constraints of the type parameter.
Writing a generic function is like using values of interface type: the
generic code can only use the operations permitted by the constraint
(or operations that are permitted for any type).
Therefore, in this design, constraints are simply interface types.
Satisfying a constraint means implementing the interface type.
(Later we'll restate this in order to define constraints for
operations other than method calls, such as [binary operators](#Operators)).
For the `Stringify` example, we need an interface type with a `String`
method that takes no arguments and returns a value of type `string`.
```
// Stringer is a type constraint that requires the type argument to have
// a String method and permits the generic function to call String.
// The String method should return a string representation of the value.
type Stringer interface {
String() string
}
```
(It doesn't matter for this discussion, but this defines the same
interface as the standard library's `fmt.Stringer` type, and real
code would likely simply use `fmt.Stringer`.)
### The `any` constraint
Now that we know that constraints are simply interface types, we can
explain what `any` means as a constraint.
As shown above, the `any` constraint permits any type as a type
argument and only permits the function to use the operations permitted
for any type.
The interface type for that is the empty interface: `interface{}`.
So we could write the `Print` example as
```
// Print prints the elements of any slice.
// Print has a type parameter T and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print[T interface{}](s []T) {
// same as above
}
```
However, it's tedious to have to write `interface{}` every time you
write a generic function that doesn't impose constraints on its type
parameters.
So in this design we suggest a type constraint `any` that is
equivalent to `interface{}`.
This will be a predeclared name, implicitly declared in the universe
block.
It will not be valid to use `any` as anything other than a type
constraint.
(Note: clearly we could make `any` generally available as an alias for
`interface{}`, or as a new defined type defined as `interface{}`.
However, we don't want this design, which is about generics, to lead
to a possibly significant change to non-generic code.
Adding `any` as a general purpose name for `interface{}` can and
should be [discussed separately](https://golang.org/issue/33232)).
### Using a constraint
For a generic function, a constraint can be thought of as the type of
the type argument: a meta-type.
As shown above, constraints appear in the type parameter list as the
meta-type of a type parameter.
```
// Stringify calls the String method on each element of s,
// and returns the results.
func Stringify[T Stringer](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}
```
The single type parameter `T` is followed by the constraint that
applies to `T`, in this case `Stringer`.
### Multiple type parameters
Although the `Stringify` example uses only a single type parameter,
functions may have multiple type parameters.
```
// Print2 has two type parameters and two non-type parameters.
func Print2[T1, T2 any](s1 []T1, s2 []T2) { ... }
```
Compare this to
```
// Print2Same has one type parameter and two non-type parameters.
func Print2Same[T any](s1 []T, s2 []T) { ... }
```
In `Print2` `s1` and `s2` may be slices of different types.
In `Print2Same` `s1` and `s2` must be slices of the same element
type.
Just as each ordinary parameter may have its own type, each type
parameter may have its own constraint.
```
// Stringer is a type constraint that requires a String method.
// The String method should return a string representation of the value.
type Stringer interface {
String() string
}
// Plusser is a type constraint that requires a Plus method.
// The Plus method is expected to add the argument to an internal
// string and return the result.
type Plusser interface {
Plus(string) string
}
// ConcatTo takes a slice of elements with a String method and a slice
// of elements with a Plus method. The slices should have the same
// number of elements. This will convert each element of s to a string,
// pass it to the Plus method of the corresponding element of p,
// and return a slice of the resulting strings.
func ConcatTo[S Stringer, P Plusser](s []S, p []P) []string {
r := make([]string, len(s))
for i, v := range s {
r[i] = p[i].Plus(v.String())
}
return r
}
```
A single constraint can be used for multiple type parameters, just as
a single type can be used for multiple non-type function parameters.
The constraint applies to each type parameter separately.
```
// Stringify2 converts two slices of different types to strings,
// and returns the concatenation of all the strings.
func Stringify2[T1, T2 Stringer](s1 []T1, s2 []T2) string {
r := ""
for _, v1 := range s1 {
r += v1.String()
}
for _, v2 := range s2 {
r += v2.String()
}
return r
}
```
### Generic types
We want more than just generic functions: we also want generic types.
We suggest that types be extended to take type parameters.
```
// Vector is a name for a slice of any element type.
type Vector[T any] []T
```
A type's type parameters are just like a function's type parameters.
Within the type definition, the type parameters may be used like any
other type.
To use a generic type, you must supply type arguments.
This is called _instantiation_.
The type arguments appear in square brackets, as usual.
When we instantiate a type by supplying type arguments for the type
parameters, we produce a type in which each use of a type parameter in
the type definition is replaced by the corresponding type argument.
```
// v is a Vector of int values.
//
// This is similar to pretending that "Vector[int]" is a valid identifier,
// and writing
// type "Vector[int]" []int
// var v "Vector[int]"
// All uses of Vector[int] will refer to the same "Vector[int]" type.
//
var v Vector[int]
```
Generic types can have methods.
The receiver type of a method must declare the same number of type
parameters as are declared in the receiver type's definition.
They are declared without any constraint.
```
// Push adds a value to the end of a vector.
func (v *Vector[T]) Push(x T) { *v = append(*v, x) }
```
The type parameters listed in a method declaration need not have the
same names as the type parameters in the type declaration.
In particular, if they are not used by the method, they can be `_`.
A generic type can refer to itself in cases where a type can
ordinarily refer to itself, but when it does so the type arguments
must be the type parameters, listed in the same order.
This restriction prevents infinite recursion of type instantiation.
```
// List is a linked list of values of type T.
type List[T any] struct {
next *List[T] // this reference to List[T] is OK
val T
}
// This type is INVALID.
type P[T1, T2 any] struct {
F *P[T2, T1] // INVALID; must be [T1, T2]
}
```
This restriction applies to both direct and indirect references.
```
// ListHead is the head of a linked list.
type ListHead[T any] struct {
head *ListElement[T]
}
// ListElement is an element in a linked list with a head.
// Each element points back to the head.
type ListElement[T any] struct {
next *ListElement[T]
val T
// Using ListHead[T] here is OK.
// ListHead[T] refers to ListElement[T] refers to ListHead[T].
// Using ListHead[int] would not be OK, as ListHead[T]
// would have an indirect reference to ListHead[int].
head *ListHead[T]
}
```
(Note: with more understanding of how people want to write code, it
may be possible to relax this rule to permit some cases that use
different type arguments.)
The type parameter of a generic type may have constraints other than
`any`.
```
// StringableVector is a slice of some type, where the type
// must have a String method.
type StringableVector[T Stringer] []T
func (s StringableVector[T]) String() string {
var sb strings.Builder
for i, v := range s {
if i > 0 {
sb.WriteString(", ")
}
// It's OK to call v.String here because v is of type T
// and T's constraint is Stringer.
sb.WriteString(v.String())
}
return sb.String()
}
```
### Methods may not take additional type arguments
Although methods of a generic type may use the type's parameters,
methods may not themselves have additional type parameters.
Where it would be useful to add type arguments to a method, people
will have to write a suitably parameterized top-level function.
There is more discussion of this in [the issues
section](#No-parameterized-methods).
### Operators
As we've seen, we are using interface types as constraints.
Interface types provide a set of methods, and nothing else.
This means that with what we've seen so far, the only thing that
generic functions can do with values of type parameters, other than
operations that are permitted for any type, is call methods.
However, method calls are not sufficient for everything we want to
express.
Consider this simple function that returns the smallest element of a
slice of values, where the slice is assumed to be non-empty.
```
// This function is INVALID.
func Smallest[T any](s []T) T {
r := s[0] // panic if slice is empty
for _, v := range s[1:] {
if v < r { // INVALID
r = v
}
}
return r
}
```
Any reasonable generics implementation should let you write this
function.
The problem is the expression `v < r`.
This assumes that `T` supports the `<` operator, but the constraint on
`T` is simply `any`.
With the `any` constraint the function `Smallest` can only use
operations that are available for all types, but not all Go types
support `<`.
Unfortunately, since `<` is not a method, there is no obvious way to
write a constraint&mdash;an interface type&mdash;that permits `<`.
We need a way to write a constraint that accepts only types that
support `<`.
In order to do that, we observe that, aside from two exceptions that
we will discuss later, all the arithmetic, comparison, and logical
operators defined by the language may only be used with types that are
predeclared by the language, or with defined types whose underlying
type is one of those predeclared types.
That is, the operator `<` can only be used with a predeclared type
such as `int` or `float64`, or a defined type whose underlying type is
one of those types.
Go does not permit using `<` with a composite type or with an
arbitrary defined type.
This means that rather than try to write a constraint for `<`, we can
approach this the other way around: instead of saying which operators
a constraint should support, we can say which types a constraint
should accept.
We do this by defining a _type set_ for a constraint.
#### Type sets
Although we are primarily interested in defining the type set of
constraints, the most straightforward approach is to define the type
set of all types.
The type set of a constraint is then constructed out of the type sets
of its elements.
This may seem like a digression from the topic of using operators with
parameterized types, but we'll get there in the end.
Every type has an associated type set.
The type set of a non-interface type `T` is simply the set `{T}`: a
set that contains just `T` itself.
The type set of an ordinary interface type is the set of all types
that declare all the methods of the interface.
Note that the type set of an ordinary interface type is an infinite
set.
For any given type `T` and interface type `IT` it's easy to tell
whether `T` is in the type set of `IT` (by checking if all methods of
`IT` are declared by `T`), but there is no reasonable way to enumerate
all the types in the type set of `IT`.
The type `IT` is a member of its own type set, because an interface
inherently declares all of its own methods.
The type set of the empty interface `interface{}` is the set of all
possible types.
It will be useful to construct the type set of an interface type by
looking at the elements of the interface.
This will produce the same result in a different way.
The elements of an interface can be either a method signature or an
embedded interface type.
Although a method signature is not a type, it's convenient to define a
type set for it: the set of all types that declare that method.
The type set of an embedded interface type `E` is simply that of `E`:
the set of all types that declare all the methods of `E`.
For any method signature `M`, the type set of `interface{ M }` is
the type of `M`: the set of all types that declare `M`.
For any method signatures `M1` and `M2`, the type set of `interface{
M1; M2 }` is set of all types that declare both `M1` and `M2`.
This is the intersection of the type set of `M1` and the type set of
`M2`.
To see this, observe that the type set of `M1` is the set of all types
with a method `M1`, and similarly for `M2`.
If we take the intersection of those two type sets, the result is the
set of all types that declare both `M1` and `M2`.
That is exactly the type set of `interface{ M1; M2 }`.
The same applies to embedded interface types.
For any two interface types `E1` and `E2`, the type set of `interface{
E1; E2 }` is the intersection of the type sets of `E1` and `E2`.
Therefore, the type set of an interface type is the intersection of
the type sets of the element of the interface.
#### Type sets of constraints
Now that we have described the type set of an interface type, we will
redefine what it means to satisfy the constraint.
Earlier we said that a type argument satisfies a constraint if it
implements the constraint.
Now we will say that a type argument satisfies a constraint if it is a
member of the constraint's type set.
For an ordinary interface type, one whose only elements are method
signatures and embedded ordinary interface types, the meaning is
exactly the same: the set of types that implement the interface type
is exactly the set of types that are in its type set.
We will now proceed to define additional elements that may appear in
an interface type that is used as a constraint, and define how those
additional elements can be used to further control the type set of the
constraint.
#### Constraint elements
The elements of an ordinary interface type are method signatures and
embedded interface types.
We propose permitting three additional elements that may be used in an
interface type used as a constraint.
If any of these additional elements are used, the interface type may
not be used as an ordinary type, but may only be used as a constraint.
##### Arbitrary type constraint element
The first new element is to simply permit listing any type, not just
an interface type.
For example: `type Integer interface{ int }`.
When a non-interface type `T` is listed as an element of a constraint,
its type set is simply `{T}`.
The type set of `int` is `{int}`.
Since the type set of a constraint is the intersection of the type
sets of all elements, the type set of `Integer` is also `{int}`.
This constraint `Integer` can be satisfied by any type that is a
member of the set `{int}`.
There is exactly one such type: `int`.
The type may be a type literal that refers to a type parameter (or
more than one), but it may not be a plain type parameter.
```
// EmbeddedParameter is INVALID.
type EmbeddedParameter[T any] interface {
T // INVALID: may not list a plain type parameter
}
```
##### Approximation constraint element
Listing a single type is useless by itself.
For constraint satisfaction, we want to be able to say not just `int`,
but "any type whose underlying type is `int`".
Consider the `Smallest` example above.
We want it to work not just for slices of the predeclared ordered
types, but also for types defined by a program.
If a program uses `type MyString string`, the program can use the `<`
operator with values of type `MyString`.
It should be possible to instantiate `Smallest` with the type
`MyString`.
To support this, the second new element we permit in a constraint is a
new syntactic construct: an approximation element, written as `~T`.
The type set of `~T` is the set of all types whose underlying type is
`T`.
For example: `type AnyString interface{ ~string }`.
The type set of `~string`, and therefore the type set of `AnyString`,
is the set of all types whose underlying type is `string`.
That includes the type `MyString`; `MyString` used as a type argument
will satisfy the constraint `AnyString`.
This new `~T` syntax will be the first use of `~` as a token in Go.
Since `~T` means the set of all types whose underlying type is `T`, it
will be an error to use `~T` with a type `T` whose underlying type is
not itself.
Types whose underlying types are themselves are:
1. Type literals, such as `[]byte` or `struct{ f int }`.
2. Most predeclared types, such as `int` or `string` (but not
`error`).
Using `~T` is not permitted if `T` is a type parameter or if `T` is an
interface type.
```
type MyString string
// AnyString matches any type whose underlying type is string.
// This includes, among others, the type string itself, and
// the type MyString.
type AnyString interface {
~string
}
// ApproximateMyString is INVALID.
type ApproximateMyString interface {
~MyString // INVALID: underlying type of MyString is not MyString
}
// ApproximateParameter is INVALID.
type ApproximateParameter[T any] interface {
~T // INVALID: T is a type parameter
}
```
##### Union constraint element
The third new element we permit in a constraint is also a new
syntactic construct: a union element, written as a series of
constraint elements separated by vertical bars (`|`).
For example: `int | float32` or `~int8 | ~int16 | ~int32 | ~int64`.
The type set of a union element is the union of the type sets of each
element in the sequence.
The elements listed in a union must all be different.
For example:
```
// PredeclaredSignedInteger is a constraint that matches the
// five predeclared signed integer types.
type PredeclaredSignedInteger interface {
int | int8 | int16 | int32 | int64
}
```
The type set of this union element is the set `{int, int8, int16,
int32, int64}`.
Since the union is the only element of `PredeclaredSignedInteger`,
that is also the type set of `PredeclaredSignedInteger`.
This constraint can be satisfied by any of those five types.
Here is an example using approximation elements:
```
// SignedInteger is a constraint that matches any signed integer type.
type SignedInteger interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
```
The type set of this constraint is the set of all types whose
underlying type is one of `int`, `int8`, `int16`, `int32`, or
`int64`.
Any of those types will satisfy this constraint.
The new constraint element syntax is
```
InterfaceType = "interface" "{" {(MethodSpec | InterfaceTypeName | ConstraintElem) ";" } "}" .
ConstraintElem = ConstraintTerm { "|" ConstraintTerm } .
ConstraintTerm = ["~"] Type .
```
#### Operations based on type sets
The purpose of type sets is to permit generic functions to use
operators, such as `<`, with values whose type is a type parameter.
The rule is that a generic function may use a value whose type is a
type parameter in any way that is permitted by every member of the
type set of the parameter's constraint.
This applies to operators like '<' or '+' or other general operators.
For special purpose operators like `range` loops, we permit their use
if the type parameter has a structural constraint, as [defined
later](#Constraint-type-inference); the definition here is basically
that the constraint has a single underlying type.
If the function can be compiled successfully using each type in the
constraint's type set, or when applicable using the structural type,
then the use is permitted.
For the `Smallest` example shown earlier, we could use a constraint
like this:
```
package constraints
// Ordered is a type constraint that matches any ordered type.
// An ordered type is one that supports the <, <=, >, and >= operators.
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
```
In practice this constraint would likely be defined and exported in a
new standard library package, `constraints`, so that it could be used
by function and type definitions.
Given that constraint, we can write this function, now valid:
```
// Smallest returns the smallest element in a slice.
// It panics if the slice is empty.
func Smallest[T constraints.Ordered](s []T) T {
r := s[0] // panics if slice is empty
for _, v := range s[1:] {
if v < r {
r = v
}
}
return r
}
```
#### Comparable types in constraints
Earlier we mentioned that there are two exceptions to the rule that
operators may only be used with types that are predeclared by the
language.
The exceptions are `==` and `!=`, which are permitted for struct,
array, and interface types.
These are useful enough that we want to be able to write a constraint
that accepts any comparable type.
To do this we introduce a new predeclared type constraint:
`comparable`.
The type set of the `comparable` constraint is the set of all
comparable types.
This permits the use of `==` and `!=` with values of that type
parameter.
For example, this function may be instantiated with any comparable
type:
```
// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}
```
Since `comparable` is a constraint, it can be embedded in another
interface type used as a constraint.
```
// ComparableHasher is a type constraint that matches all
// comparable types with a Hash method.
type ComparableHasher interface {
comparable
Hash() uintptr
}
```
The constraint `ComparableHasher` is implemented by any type that is
comparable and also has a `Hash() uintptr` method.
A generic function that uses `ComparableHasher` as a constraint can
compare values of that type and can call the `Hash` method.
It's possible to use `comparable` to produce a constraint that can not
be satisfied by any type.
See also the [discussion of empty type sets below](#Empty-type-sets).
```
// ImpossibleConstraint is a type constraint that no type can satisfy,
// because slice types are not comparable.
type ImpossibleConstraint interface {
comparable
[]int
}
```
### Mutually referencing type parameters
Within a single type parameter list, constraints may refer to any of
the other type parameters, even ones that are declared later in the
same list.
(The scope of a type parameter starts at the beginning of the type
parameter list and extends to the end of the enclosing function or
type declaration.)
For example, consider a generic graph package that contains generic
algorithms that work with graphs.
The algorithms use two types, `Node` and `Edge`.
`Node` is expected to have a method `Edges() []Edge`.
`Edge` is expected to have a method `Nodes() (Node, Node)`.
A graph can be represented as a `[]Node`.
This simple representation is enough to implement graph algorithms
like finding the shortest path.
```
package graph
// NodeConstraint is the type constraint for graph nodes:
// they must have an Edges method that returns the Edge's
// that connect to this Node.
type NodeConstraint[Edge any] interface {
Edges() []Edge
}
// EdgeConstraint is the type constraint for graph edges:
// they must have a Nodes method that returns the two Nodes
// that this edge connects.
type EdgeConstraint[Node any] interface {
Nodes() (from, to Node)
}
// Graph is a graph composed of nodes and edges.
type Graph[Node NodeConstraint[Edge], Edge EdgeConstraint[Node]] struct { ... }
// New returns a new graph given a list of nodes.
func New[Node NodeConstraint[Edge], Edge EdgeConstraint[Node]] (nodes []Node) *Graph[Node, Edge] {
...
}
// ShortestPath returns the shortest path between two nodes,
// as a list of edges.
func (g *Graph[Node, Edge]) ShortestPath(from, to Node) []Edge { ... }
```
There are a lot of type arguments and instantiations here.
In the constraint on `Node` in `Graph`, the `Edge` being passed to the
type constraint `NodeConstraint` is the second type parameter of
`Graph`.
This instantiates `NodeConstraint` with the type parameter `Edge`, so
we see that `Node` must have a method `Edges` that returns a slice of
`Edge`, which is what we want.
The same applies to the constraint on `Edge`, and the same type
parameters and constraints are repeated for the function `New`.
We aren't claiming that this is simple, but we are claiming that it is
possible.
It's worth noting that while at first glance this may look like a
typical use of interface types, `Node` and `Edge` are non-interface
types with specific methods.
In order to use `graph.Graph`, the type arguments used for `Node` and
`Edge` have to define methods that follow a certain pattern, but they
don't have to actually use interface types to do so.
In particular, the methods do not return interface types.
For example, consider these type definitions in some other package:
```
// Vertex is a node in a graph.
type Vertex struct { ... }
// Edges returns the edges connected to v.
func (v *Vertex) Edges() []*FromTo { ... }
// FromTo is an edge in a graph.
type FromTo struct { ... }
// Nodes returns the nodes that ft connects.
func (ft *FromTo) Nodes() (*Vertex, *Vertex) { ... }
```
There are no interface types here, but we can instantiate
`graph.Graph` using the type arguments `*Vertex` and `*FromTo`.
```
var g = graph.New[*Vertex, *FromTo]([]*Vertex{ ... })
```
`*Vertex` and `*FromTo` are not interface types, but when used
together they define methods that implement the constraints of
`graph.Graph`.
Note that we couldn't pass plain `Vertex` or `FromTo` to `graph.New`,
since `Vertex` and `FromTo` do not implement the constraints.
The `Edges` and `Nodes` methods are defined on the pointer types
`*Vertex` and `*FromTo`; the types `Vertex` and `FromTo` do not have
any methods.
When we use a generic interface type as a constraint, we first
instantiate the type with the type argument(s) supplied in the type
parameter list, and then compare the corresponding type argument
against the instantiated constraint.
In this example, the `Node` type argument to `graph.New` has a
constraint `NodeConstraint[Edge]`.
When we call `graph.New` with a `Node` type argument of `*Vertex` and
an `Edge` type argument of `*FromTo`, in order to check the constraint
on `Node` the compiler instantiates `NodeConstraint` with the type
argument `*FromTo`.
That produces an instantiated constraint, in this case a requirement
that `Node` have a method `Edges() []*FromTo`, and the compiler
verifies that `*Vertex` satisfies that constraint.
Although `Node` and `Edge` do not have to be instantiated with
interface types, it is also OK to use interface types if you like.
```
type NodeInterface interface { Edges() []EdgeInterface }
type EdgeInterface interface { Nodes() (NodeInterface, NodeInterface) }
```
We could instantiate `graph.Graph` with the types `NodeInterface` and
`EdgeInterface`, since they implement the type constraints.
There isn't much reason to instantiate a type this way, but it is
permitted.
This ability for type parameters to refer to other type parameters
illustrates an important point: it should be a requirement for any
attempt to add generics to Go that it be possible to instantiate
generic code with multiple type arguments that refer to each other in
ways that the compiler can check.
### Type inference
In many cases we can use type inference to avoid having to explicitly
write out some or all of the type arguments.
We can use _function argument type inference_ for a function call to
deduce type arguments from the types of the non-type arguments.
We can use _constraint type inference_ to deduce unknown type arguments
from known type arguments.
In the examples above, when instantiating a generic function or type,
we always specified type arguments for all the type parameters.
We also permit specifying just some of the type arguments, or omitting
the type arguments entirely, when the missing type arguments can be
inferred.
When only some type arguments are passed, they are the arguments for
the first type parameters in the list.
For example, a function like this:
```
func Map[F, T any](s []F, f func(F) T) []T { ... }
```
can be called in these ways. (We'll explain below how type inference
works in detail; this example is to show how an incomplete list of
type arguments is handled.)
```
var s []int
f := func(i int) int64 { return int64(i) }
var r []int64
// Specify both type arguments explicitly.
r = Map[int, int64](s, f)
// Specify just the first type argument, for F,
// and let T be inferred.
r = Map[int](s, f)
// Don't specify any type arguments, and let both be inferred.
r = Map(s, f)
```
If a generic function or type is used without specifying all the type
arguments, it is an error if any of the unspecified type arguments
cannot be inferred.
(Note: type inference is a convenience feature.
Although we think it is an important feature, it does not add any
functionality to the design, only convenience in using it.
It would be possible to omit it from the initial implementation, and
see whether it seems to be needed.
That said, this feature doesn't require additional syntax, and
produces more readable code.)
#### Type unification
Type inference is based on _type unification_.
Type unification applies to two types, either or both of which may be
or contain type parameters.
Type unification works by comparing the structure of the types.
Their structure disregarding type parameters must be identical, and
types other than type parameters must be equivalent.
A type parameter in one type may match any complete subtype in the
other type.
If the structure differs, or types other than type parameters are not
equivalent, then type unification fails.
A successful type unification provides a list of associations of type
parameters with other types (which may themselves be or contain type
parameters).
For type unification, two types that don't contain any type parameters
are equivalent if they are
[identical](https://golang.org/ref/spec#Type_identity), or if they are
channel types that are identical ignoring channel direction, or if
their underlying types are equivalent.
It's OK to permit types to not be identical during type inference,
because we will still check the constraints if inference succeeds, and
we will still check that the function arguments are assignable to the
inferred types.
For example, if `T1` and `T2` are type parameters, `[]map[int]bool`
can be unified with any of the following:
* `[]map[int]bool`
* `T1` (`T1` matches `[]map[int]bool`)
* `[]T1` (`T1` matches `map[int]bool`)
* `[]map[T1]T2` (`T1` matches `int`, `T2` matches `bool`)
(This is not an exclusive list, there are other possible successful
unifications.)
On the other hand, `[]map[int]bool` cannot be unified with any of
* `int`
* `struct{}`
* `[]struct{}`
* `[]map[T1]string`
(This list is of course also not exclusive; there are an infinite
number of types that cannot be successfully unified.)
In general we can also have type parameters on both sides, so in some
cases we might associate `T1` with, for example, `T2`, or `[]T2`.
#### Function argument type inference
Function argument type inference is used with a function call to infer
type arguments from non-type arguments.
Function argument type inference is not used when a type is
instantiated, and it is not used when a function is instantiated but
not called.
To see how it works, let's go back to [the example](#Type-parameters)
of a call to the simple `Print` function:
```
Print[int]([]int{1, 2, 3})
```
The type argument `int` in this function call can be inferred from the
type of the non-type argument.
The only type arguments that can be inferred are those that are used
for the types of the function's (non-type) input parameters.
If there are some type parameters that are used only for the
function's result parameter types, or only in the body of the
function, then those type arguments cannot be inferred using function
argument type inference.
To infer function type arguments, we unify the types of the function
call arguments with the types of the function's non-type parameters.
On the caller side we have the list of types of the actual (non-type)
arguments, which for the `Print` example is simply `[]int`.
On the function side is the list of the types of the function's
non-type parameters, which for `Print` is `[]T`.
In the lists, we discard respective arguments for which the function
side does not use a type parameter.
We must then apply type unification to the remaining argument types.
Function argument type inference is a two-pass algorithm.
In the first pass, we ignore untyped constants on the caller side and
their corresponding types in the function definition.
We use two passes so that in some cases later arguments can determine
the type of an untyped constant.
We unify corresponding types in the lists.
This will give us an association of type parameters on the function
side to types on the caller side.
If the same type parameter appears more than once on the function
side, it will match multiple argument types on the caller side.
If those caller types are not equivalent, we report an error.
After the first pass, we check any untyped constants on the caller
side.
If there are no untyped constants, or if the type parameters in the
corresponding function types have matched other input types, then
type unification is complete.
Otherwise, for the second pass, for any untyped constants whose
corresponding function types are not yet set, we determine the default
type of the untyped constant in [the usual
way](https://golang.org/ref/spec#Constants).
Then we unify the remaining types again, this time with no untyped
constants.
When constraint type inference is possible, as described below, it is
applied between the two passes.
In this example
```
s1 := []int{1, 2, 3}
Print(s1)
```
we compare `[]int` with `[]T`, match `T` with `int`, and we are done.
The single type parameter `T` is `int`, so we infer that the call to
`Print` is really a call to `Print[int]`.
For a more complex example, consider
```
// Map calls the function f on every element of the slice s,
// returning a new slice of the results.
func Map[F, T any](s []F, f func(F) T) []T {
r := make([]T, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
```
The two type parameters `F` and `T` are both used for input
parameters, so function argument type inference is possible.
In the call
```
strs := Map([]int{1, 2, 3}, strconv.Itoa)
```
we unify `[]int` with `[]F`, matching `F` with `int`.
We unify the type of `strconv.Itoa`, which is `func(int) string`,
with `func(F) T`, matching `F` with `int` and `T` with `string`.
The type parameter `F` is matched twice, both times with `int`.
Unification succeeds, so the call written as `Map` is a call of
`Map[int, string]`.
To see the untyped constant rule in effect, consider:
```
// NewPair returns a pair of values of the same type.
func NewPair[F any](f1, f2 F) *Pair[F] { ... }
```
In the call `NewPair(1, 2)` both arguments are untyped constants, so
both are ignored in the first pass.
There is nothing to unify.
We still have two untyped constants after the first pass.
Both are set to their default type, `int`.
The second run of the type unification pass unifies `F` with
`int`, so the final call is `NewPair[int](1, 2)`.
In the call `NewPair(1, int64(2))` the first argument is an untyped
constant, so we ignore it in the first pass.
We then unify `int64` with `F`.
At this point the type parameter corresponding to the untyped constant
is fully determined, so the final call is `NewPair[int64](1,
int64(2))`.
In the call `NewPair(1, 2.5)` both arguments are untyped constants,
so we move on the second pass.
This time we set the first constant to `int` and the second to
`float64`.
We then try to unify `F` with both `int` and `float64`, so unification
fails, and we report a compilation error.
As mentioned earlier, function argument type inference is done without
regard to constraints.
First we use function argument type inference to determine type
arguments to use for the function, and then, if that succeeds, we
check whether those type arguments implement the constraints (if
any).
Note that after successful function argument type inference, the
compiler must still check that the arguments can be assigned to the
parameters, as for any function call.
#### Constraint type inference
Constraint type inference permits inferring a type argument from
another type argument, based on type parameter constraints.
Constraint type inference is useful when a function wants to have a
type name for an element of some other type parameter, or when a
function wants to apply a constraint to a type that is based on some
other type parameter.
Constraint type inference can only infer types if some type parameter
has a constraint that has a type set with exactly one type in it, or a
type set for which the underlying type of every type in the type set
is the same type.
The two cases are slightly different, as in the first case, in which
the type set has exactly one type, the single type need not be its own
underlying type.
Either way, the single type is called a _structural type_, and the
constraint is called a _structural constraint_.
The structural type describes the required structure of the type
parameter.
A structural constraint may also define methods, but the methods are
ignored by constraint type inference.
For constraint type inference to be useful, the structural type will
normally be defined using one or more type parameters.
Constraint type inference is only tried if there is at least one type
parameter whose type argument is not yet known.
While the algorithm we describe here may seem complex, for typical
concrete examples it is straightforward to see what constraint type
inference will deduce.
The description of the algorithm is followed by a couple of examples.
We start by creating a mapping from type parameters to type arguments.
We initialize the mapping with all type parameters whose type
arguments are already known, if any.
For each type parameter with a structural constraint, we unify the
type parameter with the structural type.
This will have the effect of associating the type parameter with its
constraint.
We add the result into the mapping we are maintaining.
If unification finds any associations of type parameters, we add those
to the mapping as well.
When we find multiple associations of any one type parameter, we unify
each such association to produce a single mapping entry.
If a type parameter is associated directly with another type
parameter, meaning that they must both be matched with the same type,
we unify the associations of each parameter together.
If any of these various unifications fail, then constraint type
inference fails.
After merging all type parameters with structural constraints, we have
a mapping of various type parameters to types (which may be or contain
other type parameters).
We continue by looking for a type parameter `T` that is mapped to a
fully known type argument `A`, one that does not contain any type
parameters.
Anywhere that `T` appears in a type argument in the mapping, we
replace `T` with `A`.
We repeat this process until we have replaced every type parameter.
When constraint type inference is possible, type inference proceeds as
followed:
* Build the mapping using known type arguments.
* Apply constraint type inference.
* Apply function type inference using typed arguments.
* Apply constraint type inference again.
* Apply function type inference using the default types of any
remaining untyped arguments.
* Apply constraint type inference again.
##### Element constraint example
For an example of where constraint type inference is useful, let's
consider a function that takes a defined type that is a slice of
numbers, and returns an instance of that same defined type in which
each number is doubled.
It's easy to write a function similar to this if we ignore the
[defined type](https://golang.org/ref/spec#Type_definitions)
requirement.
```
// Double returns a new slice that contains all the elements of s, doubled.
func Double[E constraints.Integer](s []E) []E {
r := make([]E, len(s))
for i, v := range s {
r[i] = v + v
}
return r
}
```
However, with that definition, if we call the function with a defined
slice type, the result will not be that defined type.
```
// MySlice is a slice of ints.
type MySlice []int
// The type of V1 will be []int, not MySlice.
// Here we are using function argument type inference,
// but not constraint type inference.
var V1 = Double(MySlice{1})
```
We can do what we want by introducing a new type parameter.
```
// DoubleDefined returns a new slice that contains the elements of s,
// doubled, and also has the same type as s.
func DoubleDefined[S ~[]E, E constraints.Integer](s S) S {
// Note that here we pass S to make, where above we passed []E.
r := make(S, len(s))
for i, v := range s {
r[i] = v + v
}
return r
}
```
Now if we use explicit type arguments, we can get the right type.
```
// The type of V2 will be MySlice.
var V2 = DoubleDefined[MySlice, int](MySlice{1})
```
Function argument type inference by itself is not enough to infer the
type arguments here, because the type parameter E is not used for any
input parameter.
But a combination of function argument type inference and constraint
type inference works.
```
// The type of V3 will be MySlice.
var V3 = DoubleDefined(MySlice{1})
```
First we apply function argument type inference.
We see that the type of the argument is `MySlice`.
Function argument type inference matches the type parameter `S` with
`MySlice`.
We then move on to constraint type inference.
We know one type argument, `S`.
We see that the type argument `S` has a structural type constraint.
We create a mapping of known type arguments:
```
{S -> MySlice}
```
We then unify each type parameter with a structural constraint with
the single type in that constraint's type set.
In this case the structural constraint is `~[]E` which has the
structural type `[]E`, so we unify `S` with `[]E`.
Since we already have a mapping for `S`, we then unify `[]E` with
`MySlice`.
As `MySlice` is defined as `[]int`, that associates `E` with `int`.
We now have:
```
{S -> MySlice, E -> int}
```
We then substitute `E` with `int`, which changes nothing, and we are
done.
The type arguments for this call to `DoubleDefined` are `[MySlice,
int]`.
This example shows how we can use constraint type inference to set a
type name for an element of some other type parameter.
In this case we can name the element type of `S` as `E`, and we can
then apply further constraints to `E`, in this case requiring that it
be a number.
##### Pointer method example
Consider this example of a function that expects a type `T` that has a
`Set(string)` method that initializes a value based on a string.
```
// Setter is a type constraint that requires that the type
// implement a Set method that sets the value from a string.
type Setter interface {
Set(string)
}
// FromStrings takes a slice of strings and returns a slice of T,
// calling the Set method to set each returned value.
//
// Note that because T is only used for a result parameter,
// function argument type inference does not work when calling
// this function.
func FromStrings[T Setter](s []string) []T {
result := make([]T, len(s))
for i, v := range s {
result[i].Set(v)
}
return result
}
```
Now let's see some calling code (this example is invalid).
```
// Settable is an integer type that can be set from a string.
type Settable int
// Set sets the value of *p from a string.
func (p *Settable) Set(s string) {
i, _ := strconv.Atoi(s) // real code should not ignore the error
*p = Settable(i)
}
func F() {
// INVALID
nums := FromStrings[Settable]([]string{"1", "2"})
// Here we want nums to be []Settable{1, 2}.
...
}
```
The goal is to use `FromStrings` to get a slice of type `[]Settable`.
Unfortunately, this example is not valid and will not compile.
The problem is that `FromStrings` requires a type that has a
`Set(string)` method.
The function `F` is trying to instantiate `FromStrings` with
`Settable`, but `Settable` does not have a `Set` method.
The type that has a `Set` method is `*Settable`.
So let's rewrite `F` to use `*Settable` instead.
```
func F() {
// Compiles but does not work as desired.
// This will panic at run time when calling the Set method.
nums := FromStrings[*Settable]([]string{"1", "2"})
...
}
```
This compiles but unfortunately it will panic at run time.
The problem is that `FromStrings` creates a slice of type `[]T`.
When instantiated with `*Settable`, that means a slice of type
`[]*Settable`.
When `FromStrings` calls `result[i].Set(v)`, that invokes the `Set`
method on the pointer stored in `result[i]`.
That pointer is `nil`.
The `Settable.Set` method will be invoked with a `nil` receiver, and
will raise a panic due to a `nil` dereference error.
The pointer type `*Settable` implements the constraint, but the code
really wants to use the non-pointer type`Settable`.
What we need is a way to write `FromStrings` such that it can take the
type `Settable` as an argument but invoke a pointer method.
To repeat, we can't use `Settable` because it doesn't have a `Set`
method, and we can't use `*Settable` because then we can't create a
slice of type `Settable`.
What we can do is pass both types.
```
// Setter2 is a type constraint that requires that the type
// implement a Set method that sets the value from a string,
// and also requires that the type be a pointer to its type parameter.
type Setter2[B any] interface {
Set(string)
*B // non-interface type constraint element
}
// FromStrings2 takes a slice of strings and returns a slice of T,
// calling the Set method to set each returned value.
//
// We use two different type parameters so that we can return
// a slice of type T but call methods on *T aka PT.
// The Setter2 constraint ensures that PT is a pointer to T.
func FromStrings2[T any, PT Setter2[T]](s []string) []T {
result := make([]T, len(s))
for i, v := range s {
// The type of &result[i] is *T which is in the type set
// of Setter2, so we can convert it to PT.
p := PT(&result[i])
// PT has a Set method.
p.Set(v)
}
return result
}
```
We can then call `FromStrings2` like this:
```
func F2() {
// FromStrings2 takes two type parameters.
// The second parameter must be a pointer to the first.
// Settable is as above.
nums := FromStrings2[Settable, *Settable]([]string{"1", "2"})
// Now nums is []Settable{1, 2}.
...
}
```
This approach works as expected, but it is awkward to have to repeat
`Settable` in the type arguments.
Fortunately, constraint type inference makes it less awkward.
Using constraint type inference we can write
```
func F3() {
// Here we just pass one type argument.
nums := FromStrings2[Settable]([]string{"1", "2"})
// Now nums is []Settable{1, 2}.
...
}
```
There is no way to avoid passing the type argument `Settable`.
But given that type argument, constraint type inference can infer the
type argument `*Settable` for the type parameter `PT`.
As before, we create a mapping of known type arguments:
```
{T -> Settable}
```
We then unify each type parameter with a structural constraint.
In this case, we unify `PT` with the single type of `Setter2[T]`,
which is `*T`.
The mapping is now
```
{T -> Settable, PT -> *T}
```
We then replace `T` with `Settable` throughout, giving us:
```
{T -> Settable, PT -> *Settable}
```
After this nothing changes, and we are done.
Both type arguments are known.
This example shows how we can use constraint type inference to apply a
constraint to a type that is based on some other type parameter.
In this case we are saying that `PT`, which is `*T`, must have a `Set`
method.
We can do this without requiring the caller to explicitly mention
`*T`.
##### Constraints apply even after constraint type inference
Even when constraint type inference is used to infer type arguments
based on constraints, we must still check the constraints after the
type arguments are determined.
In the `FromStrings2` example above, we were able to deduce the type
argument for `PT` based on the `Setter2` constraint.
But in doing so we only looked at the type set, we didn't look at the
methods.
We still have to verify that the method is there, satisfying the
constraint, even if constraint type inference succeeds.
For example, consider this invalid code:
```
// Unsettable is a type that does not have a Set method.
type Unsettable int
func F4() {
// This call is INVALID.
nums := FromStrings2[Unsettable]([]string{"1", "2"})
...
}
```
When this call is made, we will apply constraint type inference just
as before.
It will succeed, just as before, and infer that the type arguments are
`[Unsettable, *Unsettable]`.
Only after constraint type inference is complete will we check whether
`*Unsettable` implements the constraint `Setter2[Unsettable]`.
Since `*Unsettable` does not have a `Set` method, constraint checking
will fail, and this code will not compile.
### Using types that refer to themselves in constraints
It can be useful for a generic function to require a type argument
with a method whose argument is the type itself.
For example, this arises naturally in comparison methods.
(Note that we are talking about methods here, not operators.)
Suppose we want to write an `Index` method that uses an `Equal` method
to check whether it has found the desired value.
We would like to write that like this:
```
// Index returns the index of e in s, or -1 if not found.
func Index[T Equaler](s []T, e T) int {
for i, v := range s {
if e.Equal(v) {
return i
}
}
return -1
}
```
In order to write the `Equaler` constraint, we have to write a
constraint that can refer to the type argument being passed in.
The easiest way to do this is to take advantage of the fact that a
constraint does not have to be a defined type, it can simply be an
interface type literal.
This interface type literal can then refer to the type parameter.
```
// Index returns the index of e in s, or -1 if not found.
func Index[T interface { Equal(T) bool }](s []T, e T) int {
// same as above
}
```
This version of `Index` would be used with a type like `equalInt`
defined here:
```
// equalInt is a version of int that implements Equaler.
type equalInt int
// The Equal method lets equalInt implement the Equaler constraint.
func (a equalInt) Equal(b equalInt) bool { return a == b }
// indexEqualInts returns the index of e in s, or -1 if not found.
func indexEqualInt(s []equalInt, e equalInt) int {
// The type argument equalInt is shown here for clarity.
// Function argument type inference would permit omitting it.
return Index[equalInt](s, e)
}
```
In this example, when we pass `equalInt` to `Index`, we check whether
`equalInt` implements the constraint `interface { Equal(T) bool }`.
The constraint has a type parameter, so we replace the type parameter
with the type argument, which is `equalInt` itself.
That gives us `interface { Equal(equalInt) bool }`.
The `equalInt` type has an `Equal` method with that signature, so all
is well, and the compilation succeeds.
### Values of type parameters are not boxed
In the current implementations of Go, interface values always hold
pointers.
Putting a non-pointer value in an interface variable causes the value
to be _boxed_.
That means that the actual value is stored somewhere else, on the heap
or stack, and the interface value holds a pointer to that location.
In this design, values of generic types are not boxed.
For example, let's look back at our earlier example of
`FromStrings2`.
When it is instantiated with type `Settable`, it returns a value of
type `[]Settable`.
For example, we can write
```
// Settable is an integer type that can be set from a string.
type Settable int
// Set sets the value of *p from a string.
func (p *Settable) Set(s string) {
// same as above
}
func F() {
// The type of nums is []Settable.
nums := FromStrings2[Settable]([]string{"1", "2"})
// Settable can be converted directly to int.
// This will set first to 1.
first := int(nums[0])
...
}
```
When we call `FromStrings2` instantiated with the type `Settable` we
get back a `[]Settable`.
The elements of that slice will be `Settable` values, which is to say,
they will be integers.
They will not be boxed, even though they were created and set by a
generic function.
Similarly, when a generic type is instantiated it will have the
expected types as components.
```
type Pair[F1, F2 any] struct {
first F1
second F2
}
```
When this is instantiated, the fields will not be boxed, and no
unexpected memory allocations will occur.
The type `Pair[int, string]` is convertible to `struct { first int;
second string }`.
### More on type sets
Let's return now to type sets to cover some less important details
that are still worth noting.
#### Both elements and methods in constraints
As seen earlier for `Setter2`, a constraint may use both constraint
elements and methods.
```
// StringableSignedInteger is a type constraint that matches any
// type that is both 1) defined as a signed integer type;
// 2) has a String method.
type StringableSignedInteger interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
String() string
}
```
The rules for type sets define what this means.
The type set of the union element is the set of all types whose
underlying type is one of the predeclared signed integer types.
The type set of `String() string` is the set of all types that define
that method.
The type set of `StringableSignedInteger` is the intersection of those
two type sets.
The result is the set of all types whose underlying type is one of the
predeclared signed integer types and that defines the method `String()
string`.
A function that uses a parameterized type `P` that uses
`StringableSignedInteger` as a constraint may use the operations
permitted for any integer type (`+`, `*`, and so forth) on a value of
type `P`.
It may also call the `String` method on a value of type `P` to get
back a `string`.
It's worth noting that the `~` is essential here.
The `StringableSignedInteger` constraint uses `~int`, not `int`.
The type `int` would not itself be permitted as a type argument, since
`int` does not have a `String` method.
An example of a type argument that would be permitted is `MyInt`,
defined as:
```
// MyInt is a stringable int.
type MyInt int
// The String method returns a string representation of mi.
func (mi MyInt) String() string {
return fmt.Sprintf("MyInt(%d)", mi)
}
```
#### Composite types in constraints
As we've seen in some earlier examples, a constraint element may be a
type literal.
```
type byteseq interface {
string | []byte
}
```
The usual rules apply: the type argument for this constraint may be
`string` or `[]byte`; a generic function with this constraint may use
any operation permitted by both `string` and `[]byte`.
The `byteseq` constraint permits writing generic functions that work
for either `string` or `[]byte` types.
```
// Join concatenates the elements of its first argument to create a
// single value. sep is placed between elements in the result.
// Join works for string and []byte types.
func Join[T byteseq](a []T, sep T) (ret T) {
if len(a) == 0 {
// Use the result parameter as a zero value;
// see discussion of zero value in the Issues section.
return ret
}
if len(a) == 1 {
// We know that a[0] is either a string or a []byte.
// We can append either a string or a []byte to a []byte,
// producing a []byte. We can convert that []byte to
// either a []byte (a no-op conversion) or a string.
return T(append([]byte(nil), a[0]...))
}
// We can call len on sep because we can call len
// on both string and []byte.
n := len(sep) * (len(a) - 1)
for _, v := range a {
// Another case where we call len on string or []byte.
n += len(v)
}
b := make([]byte, n)
// We can call copy to a []byte with an argument of
// either string or []byte.
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
// As above, we can convert b to either []byte or string.
return T(b)
}
```
For composite types (string, pointer, array, slice, struct, function,
map, channel) we impose an additional restriction: an operation may
only be used if the operator accepts identical input types (if any)
and produces identical result types for all of the types in the type
set.
To be clear, this additional restriction is only imposed when a
composite type appears in a type set.
It does not apply when a composite type is formed from a type
parameter outside of a type set, as in `var v []T` for some type
parameter `T`.
```
// structField is a type constraint whose type set consists of some
// struct types that all have a field named x.
type structField interface {
struct { a int; x int } |
struct { b int; x float64 } |
struct { c int; x uint64 }
}
// This function is INVALID.
func IncrementX[T structField](p *T) {
v := p.x // INVALID: type of p.x is not the same for all types in set
v++
p.x = v
}
// sliceOrMap is a type constraint for a slice or a map.
type sliceOrMap interface {
[]int | map[int]int
}
// Entry returns the i'th entry in a slice or the value of a map
// at key i. This is valid as the result of the operator is always int.
func Entry[T sliceOrMap](c T, i int) int {
// This is either a slice index operation or a map key lookup.
// Either way, the index and result types are type int.
return c[i]
}
// sliceOrFloatMap is a type constraint for a slice or a map.
type sliceOrFloatMap interface {
[]int | map[float64]int
}
// This function is INVALID.
// In this example the input type of the index operation is either
// int (for a slice) or float64 (for a map), so the operation is
// not permitted.
func FloatEntry[T sliceOrFloatMap](c T) int {
return c[1.0] // INVALID: input type is either int or float64.
}
```
Imposing this restriction makes it easier to reason about the type of
some operation in a generic function.
It avoids introducing the notion of a value with a constructed type
set based on applying some operation to each element of a type set.
(Note: with more understanding of how people want to write code, it
may be possible to relax this restriction in the future.)
#### Type parameters in type sets
A type literal in a constraint element can refer to type parameters of
the constraint.
In this example, the generic function `Map` takes two type parameters.
The first type parameter is required to have an underlying type that
is a slice of the second type parameter.
There are no constraints on the second type parameter.
```
// SliceConstraint is a type constraint that matches a slice of
// the type parameter.
type SliceConstraint[T any] interface {
~[]T
}
// Map takes a slice of some element type and a transformation function,
// and returns a slice of the function applied to each element.
// Map returns a slice that is the same type as its slice argument,
// even if that is a defined type.
func Map[S SliceConstraint[E], E any](s S, f func(E) E) S {
r := make(S, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
// MySlice is a simple defined type.
type MySlice []int
// DoubleMySlice takes a value of type MySlice and returns a new
// MySlice value with each element doubled in value.
func DoubleMySlice(s MySlice) MySlice {
// The type arguments listed explicitly here could be inferred.
v := Map[MySlice, int](s, func(e int) int { return 2 * e })
// Here v has type MySlice, not type []int.
return v
}
```
We showed other examples of this earlier in the discussion of
[constraint type inference](#Constraint-type-inference).
#### Type conversions
In a function with two type parameters `From` and `To`, a value of
type `From` may be converted to a value of type `To` if all the types
in the type set of `From`'s constraint can be converted to all the
types in the type set of `To`'s constraint.
This is a consequence of the general rule that a generic function may
use any operation that is permitted by all types listed in the type
set.
For example:
```
type integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
func Convert[To, From integer](from From) To {
to := To(from)
if From(to) != from {
panic("conversion out of range")
}
return to
}
```
The type conversions in `Convert` are permitted because Go permits
every integer type to be converted to every other integer type.
#### Untyped constants
Some functions use untyped constants.
An untyped constant is permitted with a value of a type parameter if
it is permitted with every type in the type set of the type
parameter's constraint.
As with type conversions, this is a consequence of the general rule
that a generic function may use any operation that is permitted by all
types in the type set.
```
type integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
func Add10[T integer](s []T) {
for i, v := range s {
s[i] = v + 10 // OK: 10 can convert to any integer type
}
}
// This function is INVALID.
func Add1024[T integer](s []T) {
for i, v := range s {
s[i] = v + 1024 // INVALID: 1024 not permitted by int8/uint8
}
}
```
#### Type sets of embedded constraints
When a constraint embeds another constraint, the type set of the
outer constraint is the intersection of all the type sets involved.
If there are multiple embedded types, intersection preserves the
property that any type argument must satisfy the requirements of all
constraint elements.
```
// Addable is types that support the + operator.
type Addable interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 | ~complex64 | ~complex128 |
~string
}
// Byteseq is a byte sequence: either string or []byte.
type Byteseq interface {
~string | ~[]byte
}
// AddableByteseq is a byte sequence that supports +.
// This is every type that is both Addable and Byteseq.
// In other words, just the type set ~string.
type AddableByteseq interface {
Addable
Byteseq
}
```
An embedded constraint may appear in a union element.
The type set of the union is, as usual, the union of the type sets of
the elements listed in the union.
```
// Signed is a constraint with a type set of all signed integer
// types.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint with a type set of all unsigned integer
// types.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint with a type set of all integer types.
type Integer interface {
Signed | Unsigned
}
```
#### Interface types in union elements
We've said that the type set of a union element is the union of the
type sets of all types in the union.
For most types `T` the type set of `T` is simply `T` itself.
For interface types (and approximation elements), however, that is not
the case.
The type set of an interface type that does not embed a non-interface
element is, as we said earlier, the set of all types that declare all
the methods of the interface, including the interface type itself.
Using such an interface type in a union element will add that type set
to the union.
```
type Stringish interface {
string | fmt.Stringer
}
```
The type set of `Stringish` is the type `string` and all types that
implement `fmt.Stringer`.
Any of those types (including `fmt.Stringer` itself) will be permitted
as a type argument for this constraint.
No operations will be permitted for a value of a type parameter that
uses `Stringish` as a constraint (other than operations supported by
all types).
This is because `fmt.Stringer` is in the type set of `Stringish`, and
`fmt.Stringer`, an interface type, does not support any type-specific
operations.
The operations permitted by `Stringish` are those operations supported
by all the types in the type set, including `fmt.Stringer`, so in this
case there are no operations other than those supported by all types.
A parameterized function that uses this constraint will have to use
type assertions or reflection in order to use the values.
Still, this may be useful in some cases for stronger static type
checking.
The main point is that it follows directly from the definition of type
sets and constraint satisfaction.
#### Empty type sets
It is possible to write a constraint with an empty type set.
There is no type argument that will satisfy such a constraint,
so any attempt to instantiate a function that uses constraint with an
empty type set will fail.
It is not possible in general for the compiler to detect all such
cases.
Probably the vet tool should give an error for cases that it is able
to detect.
```
// Unsatisfiable is an unsatisfiable constraint with an empty type set.
// No predeclared types have any methods.
// If this used ~int | ~float32 the type set would not be empty.
type Unsatisfiable interface {
int | float32
String() string
}
```
#### General notes on type sets
It may seem awkward to explicitly list types in a constraint, but it
is clear both as to which type arguments are permitted at the call
site, and which operations are permitted by the generic function.
If the language later changes to support operator methods (there are
no such plans at present), then constraints will handle them as they
do any other kind of method.
There will always be a limited number of predeclared types, and a
limited number of operators that those types support.
Future language changes will not fundamentally change those facts, so
this approach will continue to be useful.
This approach does not attempt to handle every possible operator.
The expectation is that composite types will normally be handled using
composite types in generic function and type declarations, rather than
putting composite types in a type set.
For example, we expect functions that want to index into a slice to be
parameterized on the slice element type `T`, and to use parameters or
variables of type `[]T`.
As shown in the `DoubleMySlice` example above, this approach makes it
awkward to declare generic functions that accept and return a
composite type and want to return the same result type as their
argument type.
Defined composite types are not common, but they do arise.
This awkwardness is a weakness of this approach.
Constraint type inference can help at the call site.
### Reflection
We do not propose to change the reflect package in any way.
When a type or function is instantiated, all of the type parameters
will become ordinary non-generic types.
The `String` method of a `reflect.Type` value of an instantiated type
will return the name with the type arguments in square brackets.
For example, `List[int]`.
It's impossible for non-generic code to refer to generic code without
instantiating it, so there is no reflection information for
uninstantiated generic types or functions.
### Implementation
Russ Cox [famously observed](https://research.swtch.com/generic) that
generics require choosing among slow programmers, slow compilers, or
slow execution times.
We believe that this design permits different implementation choices.
Code may be compiled separately for each set of type arguments, or it
may be compiled as though each type argument is handled similarly to
an interface type with method calls, or there may be some combination
of the two.
In other words, this design permits people to stop choosing slow
programmers, and permits the implementation to decide between slow
compilers (compile each set of type arguments separately) or slow
execution times (use method calls for each operation on a value of a
type argument).
### Summary
While this document is long and detailed, the actual design reduces to
a few major points.
* Functions and types can have type parameters, which are defined
using constraints, which are interface types.
* Constraints describe the methods required and the types permitted
for a type argument.
* Constraints describe the methods and operations permitted for a type
parameter.
* Type inference will often permit omitting type arguments when
calling functions with type parameters.
This design is completely backward compatible.
We believe that this design addresses people's needs for generic
programming in Go, without making the language any more complex than
necessary.
We can't truly know the impact on the language without years of
experience with this design.
That said, here are some speculations.
#### Complexity
One of the great aspects of Go is its simplicity.
Clearly this design makes the language more complex.
We believe that the increased complexity is small for people reading
well written generic code, rather than writing it.
Naturally people must learn the new syntax for declaring type
parameters.
This new syntax, and the new support for type sets in interfaces, are
the only new syntactic constructs in this design.
The code within a generic function reads like ordinary Go code, as can
be seen in the examples below.
It is an easy shift to go from `[]int` to `[]T`.
Type parameter constraints serve effectively as documentation,
describing the type.
We expect that most packages will not define generic types or
functions, but many packages are likely to use generic types or
functions defined elsewhere.
In the common case, generic functions work exactly like non-generic
functions: you simply call them.
Type inference means that you do not have to write out the type
arguments explicitly.
The type inference rules are designed to be unsurprising: either the
type arguments are deduced correctly, or the call fails and requires
explicit type parameters.
Type inference uses type equivalence, with no attempt to resolve two
types that are similar but not equivalent, which removes significant
complexity.
Packages using generic types will have to pass explicit type
arguments.
The syntax for this is straightforward.
The only change is passing arguments to types rather than only to
functions.
In general, we have tried to avoid surprises in the design.
Only time will tell whether we succeeded.
#### Pervasiveness
We expect that a few new packages will be added to the standard
library.
A new `slices` packages will be similar to the existing bytes and
strings packages, operating on slices of any element type.
New `maps` and `chans` packages will provide algorithms that are
currently duplicated for each element type.
A `sets` package may be added.
A new `constraints` package will provide standard constraints, such as
constraints that permit all integer types or all numeric types.
Packages like `container/list` and `container/ring`, and types like
`sync.Map` and `sync/atomic.Value`, will be updated to be compile-time
type-safe, either using new names or new versions of the packages.
The `math` package will be extended to provide a set of simple
standard algorithms for all numeric types, such as the ever popular
`Min` and `Max` functions.
We may add generic variants to the `sort` package.
It is likely that new special purpose compile-time type-safe container
types will be developed.
We do not expect approaches like the C++ STL iterator types to become
widely used.
In Go that sort of idea is more naturally expressed using an interface
type.
In C++ terms, using an interface type for an iterator can be seen as
carrying an abstraction penalty, in that run-time efficiency will be
less than C++ approaches that in effect inline all code; we believe
that Go programmers will continue to find that sort of penalty to be
acceptable.
As we get more container types, we may develop a standard `Iterator`
interface.
That may in turn lead to pressure to modify the language to add some
mechanism for using an `Iterator` with the `range` clause.
That is very speculative, though.
#### Efficiency
It is not clear what sort of efficiency people expect from generic
code.
Generic functions, rather than generic types, can probably be compiled
using an interface-based approach.
That will optimize compile time, in that the function is only compiled
once, but there will be some run time cost.
Generic types may most naturally be compiled multiple times for each
set of type arguments.
This will clearly carry a compile time cost, but there shouldn't be
any run time cost.
Compilers can also choose to implement generic types similarly to
interface types, using special purpose methods to access each element
that depends on a type parameter.
Only experience will show what people expect in this area.
#### Omissions
We believe that this design covers the basic requirements for
generic programming.
However, there are a number of programming constructs that are not
supported.
* No specialization.
There is no way to write multiple versions of a generic function
that are designed to work with specific type arguments.
* No metaprogramming.
There is no way to write code that is executed at compile time to
generate code to be executed at run time.
* No higher level abstraction.
There is no way to use a function with type arguments other than to
call it or instantiate it.
There is no way to use a generic type other than to instantiate it.
* No general type description.
In order to use operators in a generic function, constraints list
specific types, rather than describing the characteristics that a
type must have.
This is easy to understand but may be limiting at times.
* No covariance or contravariance of function parameters.
* No operator methods.
You can write a generic container that is compile-time type-safe,
but you can only access it with ordinary methods, not with syntax
like `c[k]`.
* No currying.
There is no way to partially instantiate a generic function or type,
other than by using a helper function or a wrapper type.
All type arguments must be either explicitly passed or inferred at
instantiation time.
* No variadic type parameters.
There is no support for variadic type parameters, which would permit
writing a single generic function that takes different numbers of
both type parameters and regular parameters.
* No adaptors.
There is no way for a constraint to define adaptors that could be
used to support type arguments that do not already implement the
constraint, such as, for example, defining an `==` operator in terms
of an `Equal` method, or vice-versa.
* No parameterization on non-type values such as constants.
This arises most obviously for arrays, where it might sometimes be
convenient to write `type Matrix[n int] [n][n]float64`.
It might also sometimes be useful to specify significant values for
a container type, such as a default value for elements.
#### Issues
There are some issues with this design that deserve a more detailed
discussion.
We think these issues are relatively minor compared to the design as a
whole, but they still deserve a complete hearing and discussion.
##### The zero value
This design has no simple expression for the zero value of a type
parameter.
For example, consider this implementation of optional values that uses
pointers:
```
type Optional[T any] struct {
p *T
}
func (o Optional[T]) Val() T {
if o.p != nil {
return *o.p
}
var zero T
return zero
}
```
In the case where `o.p == nil`, we want to return the zero value of
`T`, but we have no way to write that.
It would be nice to be able to write `return nil`, but that wouldn't
work if `T` is, say, `int`; in that case we would have to write
`return 0`.
And, of course, there is no way to write a constraint to support
either `return nil` or `return 0`.
Some approaches to this are:
* Use `var zero T`, as above, which works with the existing design
but requires an extra statement.
* Use `*new(T)`, which is cryptic but works with the existing
design.
* For results only, name the result parameter, and use a naked
`return` statement to return the zero value.
* Extend the design to permit using `nil` as the zero value of any
generic type (but see [issue 22729](https://golang.org/issue/22729)).
* Extend the design to permit using `T{}`, where `T` is a type
parameter, to indicate the zero value of the type.
* Change the language to permit using `_` on the right hand of an
assignment (including `return` or a function call) as proposed in
[issue 19642](https://golang.org/issue/19642).
* Change the language to permit `return ...` to return zero values of
the result types, as proposed in
[issue 21182](https://golang.org/issue/21182).
We feel that more experience with this design is needed before
deciding what, if anything, to do here.
##### Identifying the matched predeclared type
The design doesn't provide any way to test the underlying type matched
by a `~T` constraint element.
Code can test the actual type argument through the somewhat awkward
approach of converting to an empty interface type and using a type
assertion or a type switch.
But that lets code test the actual type argument, which is not the
same as the underlying type.
Here is an example that shows the difference.
```
type Float interface {
~float32 | ~float64
}
func NewtonSqrt[T Float](v T) T {
var iterations int
switch (interface{})(v).(type) {
case float32:
iterations = 4
case float64:
iterations = 5
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
// Code omitted.
}
type MyFloat float32
var G = NewtonSqrt(MyFloat(64))
```
This code will panic when initializing `G`, because the type of `v` in
the `NewtonSqrt` function will be `MyFloat`, not `float32` or
`float64`.
What this function actually wants to test is not the type of `v`, but
the approximate type that `v` matched in the constraint's type set.
One way to handle this would be to permit writing approximate types in
a type switch, as in `case ~float32:`.
Such a case would match any type whose underlying type is `float32`.
This would be meaningful, and potentially useful, even in type
switches outside of generic functions.
##### No way to express convertibility
The design has no way to express convertibility between two different
type parameters.
For example, there is no way to write this function:
```
// Copy copies values from src to dst, converting them as they go.
// It returns the number of items copied, which is the minimum of
// the lengths of dst and src.
// This implementation is INVALID.
func Copy[T1, T2 any](dst []T1, src []T2) int {
for i, x := range src {
if i > len(dst) {
return i
}
dst[i] = T1(x) // INVALID
}
return len(src)
}
```
The conversion from type `T2` to type `T1` is invalid, as there is no
constraint on either type that permits the conversion.
Worse, there is no way to write such a constraint in general.
In the particular case where both `T1` and `T2` have limited type sets
this function can be written as described earlier when discussing
[type conversions using type sets](#Type-conversions).
But, for example, there is no way to write a constraint for the case
in which `T1` is an interface type and `T2` is a type that implements
that interface.
It's worth noting that if `T1` is an interface type then this can be
written using a conversion to the empty interface type and a type
assertion, but this is, of course, not compile-time type-safe.
```
// Copy copies values from src to dst, converting them as they go.
// It returns the number of items copied, which is the minimum of
// the lengths of dst and src.
func Copy[T1, T2 any](dst []T1, src []T2) int {
for i, x := range src {
if i > len(dst) {
return i
}
dst[i] = (interface{})(x).(T1)
}
return len(src)
}
```
##### No parameterized methods
This design does not permit methods to declare type parameters that
are specific to the method.
The receiver may have type parameters, but the method may not add any
type parameters.
In Go, one of the main roles of methods is to permit types to
implement interfaces.
It is not clear whether it is reasonably possible to permit
parameterized methods to implement interfaces.
For example, consider this code, which uses the obvious syntax for
parameterized methods.
This code uses multiple packages to make the problem clearer.
```
package p1
// S is a type with a parameterized method Identity.
type S struct{}
// Identity is a simple identity method that works for any type.
func (S) Identity[T any](v T) T { return v }
package p2
// HasIdentity is an interface that matches any type with a
// parameterized Identity method.
type HasIdentity interface {
Identity[T any](T) T
}
package p3
import "p2"
// CheckIdentity checks the Identity method if it exists.
// Note that although this function calls a parameterized method,
// this function is not itself parameterized.
func CheckIdentity(v interface{}) {
if vi, ok := v.(p2.HasIdentity); ok {
if got := vi.Identity[int](0); got != 0 {
panic(got)
}
}
}
package p4
import (
"p1"
"p3"
)
// CheckSIdentity passes an S value to CheckIdentity.
func CheckSIdentity() {
p3.CheckIdentity(p1.S{})
}
```
In this example, we have a type `p1.S` with a parameterized method and
a type `p2.HasIdentity` that also has a parameterized method.
`p1.S` implements `p2.HasIdentity`.
Therefore, the function `p3.CheckIdentity` can call `vi.Identity` with
an `int` argument, which in the call from `p4.CheckSIdentity` will be
a call to `p1.S.Identity[int]`.
But package p3 does not know anything about the type `p1.S`.
There may be no other call to `p1.S.Identity` elsewhere in the
program.
We need to instantiate `p1.S.Identity[int]` somewhere, but how?
We could instantiate it at link time, but in the general case that
requires the linker to traverse the complete call graph of the program
to determine the set of types that might be passed to `CheckIdentity`.
And even that traversal is not sufficient in the general case when
type reflection gets involved, as reflection might look up methods
based on strings input by the user.
So in general instantiating parameterized methods in the linker might
require instantiating every parameterized method for every possible
type argument, which seems untenable.
Or, we could instantiate it at run time.
In general this means using some sort of JIT, or compiling the code to
use some sort of reflection based approach.
Either approach would be very complex to implement, and would be
surprisingly slow at run time.
Or, we could decide that parameterized methods do not, in fact,
implement interfaces, but then it's much less clear why we need
methods at all.
If we disregard interfaces, any parameterized method can be
implemented as a parameterized function.
So while parameterized methods seem clearly useful at first glance, we
would have to decide what they mean and how to implement that.
##### No way to require pointer methods
In some cases a parameterized function is naturally written such that
it always invokes methods on addressable values.
For example, this happens when calling a method on each element of a
slice.
In such a case, the function only requires that the method be in the
slice element type's pointer method set.
The type constraints described in this design have no way to write
that requirement.
For example, consider a variant of the `Stringify` example we [showed
earlier](#Using-a-constraint).
```
// Stringify2 calls the String method on each element of s,
// and returns the results.
func Stringify2[T Stringer](s []T) (ret []string) {
for i := range s {
ret = append(ret, s[i].String())
}
return ret
}
```
Suppose we have a `[]bytes.Buffer` and we want to convert it into a
`[]string`.
The `Stringify2` function here won't help us.
We want to write `Stringify2[bytes.Buffer]`, but we can't, because
`bytes.Buffer` doesn't have a `String` method.
The type that has a `String` method is `*bytes.Buffer`.
Writing `Stringify2[*bytes.Buffer]` doesn't help because that function
expects a `[]*bytes.Buffer`, but we have a `[]bytes.Buffer`.
We discussed a similar case in the [pointer method
example](#Pointer-method-example) above.
There we used constraint type inference to help simplify the problem.
Here that doesn't help, because `Stringify2` doesn't really care about
calling a pointer method.
It just wants a type that has a `String` method, and it's OK if the
method is only in the pointer method set, not the value method set.
But we also want to accept the case where the method is in the value
method set, for example if we really do have a `[]*bytes.Buffer`.
What we need is a way to say that the type constraint applies to
either the pointer method set or the value method set.
The body of the function would be required to only call the method on
addressable values of the type.
It's not clear how often this problem comes up in practice.
##### No association between float and complex
Constraint type inference lets us give a name to the element of a
slice type, and to apply other similar type decompositions.
However, there is no way to associate a float type and a complex type.
For example, there is no way to write the predeclared `real`, `imag`,
or `complex` functions with this design.
There is no way to say "if the argument type is `complex64`, then the
result type is `float32`."
One possible approach here would be to permit `real(T)` as a type
constraint meaning "the float type associated with the complex type
`T`".
Similarly, `complex(T)` would mean "the complex type associated with
the floating point type `T`".
Constraint type inference would simplify the call site.
However, that would be unlike other type constraints.
#### Discarded ideas
This design is not perfect, and there may be ways to improve it.
That said, there are many ideas that we've already considered in
detail.
This section lists some of those ideas in the hopes that it will help
to reduce repetitive discussion.
The ideas are presented in the form of a FAQ.
##### What happened to contracts?
An earlier draft design of generics implemented constraints using a
new language construct called contracts.
Type sets appeared only in contracts, rather than in interface types.
However, many people had a hard time understanding the difference
between contracts and interface types.
It also turned out that contracts could be represented as a set of
corresponding interfaces; there was no loss in expressive power
without contracts.
We decided to simplify the approach to use only interface types.
##### Why not use methods instead of type sets?
_Type sets are weird._
_Why not write methods for all operators?_
It is possible to permit operator tokens as method names, leading to
methods such as `+(T) T`.
Unfortunately, that is not sufficient.
We would need some mechanism to describe a type that matches any
integer type, for operations such as shifts `<<(integer) T` and
indexing `[](integer) T` which are not restricted to a single int
type.
We would need an untyped boolean type for operations such as `==(T)
untyped bool`.
We would need to introduce new notation for operations such as
conversions, or to express that one may range over a type, which would
likely require some new syntax.
We would need some mechanism to describe valid values of untyped
constants.
We would have to consider whether support for `<(T) bool` means that a
generic function can also use `<=`, and similarly whether support for
`+(T) T` means that a function can also use `++`.
It might be possible to make this approach work but it's not
straightforward.
The approach used in this design seems simpler and relies on only one
new syntactic construct (type sets) and one new name (`comparable`).
##### Why not put type parameters on packages?
We investigated this extensively.
It becomes problematic when you want to write a `list` package, and
you want that package to include a `Transform` function that converts
a `List` of one element type to a `List` of another element type.
It's very awkward for a function in one instantiation of a package to
return a type that requires a different instantiation of the same
package.
It also confuses package boundaries with type definitions.
There is no particular reason to think that the uses of generic types
will break down neatly into packages.
Sometimes they will, sometimes they won't.
##### Why not use the syntax `F<T>` like C++ and Java?
When parsing code within a function, such as `v := F<T>`, at the point
of seeing the `<` it's ambiguous whether we are seeing a type
instantiation or an expression using the `<` operator.
This is very difficult to resolve without type information.
For example, consider a statement like
```
a, b = w < x, y > (z)
```
Without type information, it is impossible to decide whether the right
hand side of the assignment is a pair of expressions (`w < x` and `y >
(z)`), or whether it is a generic function instantiation and call that
returns two result values (`(w<x, y>)(z)`).
It is a key design decision of Go that parsing be possible without
type information, which seems impossible when using angle brackets for
generics.
##### Why not use the syntax `F(T)`?
An earlier version of this design used that syntax.
It was workable but it introduced several parsing ambiguities.
For example, when writing `var f func(x(T))` it wasn't clear whether
the type was a function with a single unnamed parameter of the
instantiated type `x(T)` or whether it was a function with a parameter
named `x` with type `(T)` (more usually written as `func(x T)`, but in
this case with a parenthesized type).
There were other ambiguities as well.
For `[]T(v1)` and `[]T(v2){}`, at the point of the open parentheses we
don't know whether this is a type conversion (of the value `v1` to the
type `[]T`) or a type literal (whose type is the instantiated type
`T(v2)`).
For `interface { M(T) }` we don't know whether this an interface with
a method `M` or an interface with an embedded instantiated interface
`M(T)`.
These ambiguities are solvable, by adding more parentheses, but
awkward.
Also some people were troubled by the number of parenthesized lists
involved in declarations like `func F(T any)(v T)(r1, r2 T)` or in
calls like `F(int)(1)`.
##### Why not use `F«T»`?
We considered it but we couldn't bring ourselves to require
non-ASCII.
##### Why not define constraints in a builtin package?
_Instead of writing out type sets, use names like_
_`constraints.Arithmetic` and `constraints.Comparable`._
Listing all the possible combinations of types gets rather lengthy.
It also introduces a new set of names that not only the writer of
generic code, but, more importantly, the reader, must remember.
One of the driving goals of this design is to introduce as few new
names as possible.
In this design we introduce only two new predeclared names,
`comparable` and `any`.
We expect that if people find such names useful, we can introduce a
package `constraints` that defines those names in the form of
constraints that can be used by other types and functions and embedded
in other constraints.
That will define the most useful names in the standard library while
giving programmers the flexibility to use other combinations of types
where appropriate.
##### Why not permit type assertions on values whose type is a type parameter?
In an earlier version of this design, we permitted using type
assertions and type switches on variables whose type was a type
parameter, or whose type was based on a type parameter.
We removed this facility because it is always possible to convert a
value of any type to the empty interface type, and then use a type
assertion or type switch on that.
Also, it was sometimes confusing that in a constraint with a type
set that uses approximation elements, a type assertion or type switch
would use the actual type argument, not the underlying type of the
type argument (the difference is explained in the section on
[identifying the matched predeclared type](#Identifying-the-matched-predeclared-type)).
#### Comparison with Java
Most complaints about Java generics center around type erasure.
This design does not have type erasure.
The reflection information for a generic type will include the full
compile-time type information.
In Java type wildcards (`List<? extends Number>`, `List<? super
Number>`) implement covariance and contravariance.
These concepts are missing from Go, which makes generic types much
simpler.
#### Comparison with C++
C++ templates do not enforce any constraints on the type arguments
(unless the concept proposal is adopted).
This means that changing template code can accidentally break far-off
instantiations.
It also means that error messages are reported only at instantiation
time, and can be deeply nested and difficult to understand.
This design avoids these problems through mandatory and explicit
constraints.
C++ supports template metaprogramming, which can be thought of as
ordinary programming done at compile time using a syntax that is
completely different than that of non-template C++.
This design has no similar feature.
This saves considerable complexity while losing some power and run
time efficiency.
C++ uses two-phase name lookup, in which some names are looked up in
the context of the template definition, and some names are looked up
in the context of the template instantiation.
In this design all names are looked up at the point where they are
written.
In practice, all C++ compilers compile each template at the point
where it is instantiated.
This can slow down compilation time.
This design offers flexibility as to how to handle the compilation of
generic functions.
#### Comparison with Rust
The generics described in this design are similar to generics in
Rust.
One difference is that in Rust the association between a trait bound
and a type must be defined explicitly, either in the crate that
defines the trait bound or the crate that defines the type.
In Go terms, this would mean that we would have to declare somewhere
whether a type satisfied a constraint.
Just as Go types can satisfy Go interfaces without an explicit
declaration, in this design Go type arguments can satisfy a constraint
without an explicit declaration.
Where this design uses type sets, the Rust standard library defines
standard traits for operations like comparison.
These standard traits are automatically implemented by Rust's
primitive types, and can be implemented by user defined types as
well.
Rust provides a fairly extensive list of traits, at least 34, covering
all of the operators.
Rust supports type parameters on methods, which this design does not.
## Examples
The following sections are examples of how this design could be used.
This is intended to address specific areas where people have created
user experience reports concerned with Go's lack of generics.
### Map/Reduce/Filter
Here is an example of how to write map, reduce, and filter functions
for slices.
These functions are intended to correspond to the similar functions in
Lisp, Python, Java, and so forth.
```
// Package slices implements various slice algorithms.
package slices
// Map turns a []T1 to a []T2 using a mapping function.
// This function has two type parameters, T1 and T2.
// This works with slices of any type.
func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {
r := make([]T2, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
// Reduce reduces a []T1 to a single value using a reduction function.
func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 {
r := initializer
for _, v := range s {
r = f(r, v)
}
return r
}
// Filter filters values from a slice using a filter function.
// It returns a new slice with only the elements of s
// for which f returned true.
func Filter[T any](s []T, f func(T) bool) []T {
var r []T
for _, v := range s {
if f(v) {
r = append(r, v)
}
}
return r
}
```
Here are some example calls of these functions.
Type inference is used to determine the type arguments based on the
types of the non-type arguments.
```
s := []int{1, 2, 3}
floats := slices.Map(s, func(i int) float64 { return float64(i) })
// Now floats is []float64{1.0, 2.0, 3.0}.
sum := slices.Reduce(s, 0, func(i, j int) int { return i + j })
// Now sum is 6.
evens := slices.Filter(s, func(i int) bool { return i%2 == 0 })
// Now evens is []int{2}.
```
### Map keys
Here is how to get a slice of the keys of any map.
```
// Package maps provides general functions that work for all map types.
package maps
// Keys returns the keys of the map m in a slice.
// The keys will be returned in an unpredictable order.
// This function has two type parameters, K and V.
// Map keys must be comparable, so key has the predeclared
// constraint comparable. Map values can be any type.
func Keys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
```
In typical use the map key and val types will be inferred.
```
k := maps.Keys(map[int]int{1:2, 2:4})
// Now k is either []int{1, 2} or []int{2, 1}.
```
### Sets
Many people have asked for Go's builtin map type to be extended, or
rather reduced, to support a set type.
Here is a type-safe implementation of a set type, albeit one that uses
methods rather than operators like `[]`.
```
// Package sets implements sets of any comparable type.
package sets
// Set is a set of values.
type Set[T comparable] map[T]struct{}
// Make returns a set of some element type.
func Make[T comparable]() Set[T] {
return make(Set[T])
}
// Add adds v to the set s.
// If v is already in s this has no effect.
func (s Set[T]) Add(v T) {
s[v] = struct{}{}
}
// Delete removes v from the set s.
// If v is not in s this has no effect.
func (s Set[T]) Delete(v T) {
delete(s, v)
}
// Contains reports whether v is in s.
func (s Set[T]) Contains(v T) bool {
_, ok := s[v]
return ok
}
// Len reports the number of elements in s.
func (s Set[T]) Len() int {
return len(s)
}
// Iterate invokes f on each element of s.
// It's OK for f to call the Delete method.
func (s Set[T]) Iterate(f func(T)) {
for v := range s {
f(v)
}
}
```
Example use:
```
// Create a set of ints.
// We pass int as a type argument.
// Then we write () because Make does not take any non-type arguments.
// We have to pass an explicit type argument to Make.
// Function argument type inference doesn't work because the
// type argument to Make is only used for a result parameter type.
s := sets.Make[int]()
// Add the value 1 to the set s.
s.Add(1)
// Check that s does not contain the value 2.
if s.Contains(2) { panic("unexpected 2") }
```
This example shows how to use this design to provide a compile-time
type-safe wrapper around an existing API.
### Sort
Before the introduction of `sort.Slice`, a common complaint was the
need for boilerplate definitions in order to use `sort.Sort`.
With this design, we can add to the sort package as follows:
```
// Ordered is a type constraint that matches all ordered types.
// (An ordered type is one that supports the < <= >= > operators.)
// In practice this type constraint would likely be defined in
// a standard library package.
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
// orderedSlice is an internal type that implements sort.Interface.
// The Less method uses the < operator. The Ordered type constraint
// ensures that T has a < operator.
type orderedSlice[T Ordered] []T
func (s orderedSlice[T]) Len() int { return len(s) }
func (s orderedSlice[T]) Less(i, j int) bool { return s[i] < s[j] }
func (s orderedSlice[T]) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// OrderedSlice sorts the slice s in ascending order.
// The elements of s must be ordered using the < operator.
func OrderedSlice[T Ordered](s []T) {
// Convert s to the type orderedSlice[T].
// As s is []T, and orderedSlice[T] is defined as []T,
// this conversion is permitted.
// orderedSlice[T] implements sort.Interface,
// so can pass the result to sort.Sort.
// The elements will be sorted using the < operator.
sort.Sort(orderedSlice[T](s))
}
```
Now we can write:
```
s1 := []int32{3, 5, 2}
sort.OrderedSlice(s1)
// Now s1 is []int32{2, 3, 5}
s2 := []string{"a", "c", "b"})
sort.OrderedSlice(s2)
// Now s2 is []string{"a", "b", "c"}
```
Along the same lines, we can add a function for sorting using a
comparison function, similar to `sort.Slice` but writing the function
to take values rather than slice indexes.
```
// sliceFn is an internal type that implements sort.Interface.
// The Less method calls the cmp field.
type sliceFn[T any] struct {
s []T
cmp func(T, T) bool
}
func (s sliceFn[T]) Len() int { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.cmp(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }
// SliceFn sorts the slice s according to the function cmp.
func SliceFn[T any](s []T, cmp func(T, T) bool) {
sort.Sort(sliceFn[T]{s, cmp})
}
```
An example of calling this might be:
```
var s []*Person
// ...
sort.SliceFn(s, func(p1, p2 *Person) bool { return p1.Name < p2.Name })
```
### Channels
Many simple general purpose channel functions are never written,
because they must be written using reflection and the caller must type
assert the results.
With this design they become straightforward to write.
```
// Package chans implements various channel algorithms.
package chans
import "runtime"
// Drain drains any elements remaining on the channel.
func Drain[T any](c <-chan T) {
for range c {
}
}
// Merge merges two channels of some element type into a single channel.
func Merge[T any](c1, c2 <-chan T) <-chan T {
r := make(chan T)
go func(c1, c2 <-chan T, r chan<- T) {
defer close(r)
for c1 != nil || c2 != nil {
select {
case v1, ok := <-c1:
if ok {
r <- v1
} else {
c1 = nil
}
case v2, ok := <-c2:
if ok {
r <- v2
} else {
c2 = nil
}
}
}
}(c1, c2, r)
return r
}
// Ranger provides a convenient way to exit a goroutine sending values
// when the receiver stops reading them.
//
// Ranger returns a Sender and a Receiver. The Receiver provides a
// Next method to retrieve values. The Sender provides a Send method
// to send values and a Close method to stop sending values. The Next
// method indicates when the Sender has been closed, and the Send
// method indicates when the Receiver has been freed.
func Ranger[T any]() (*Sender[T], *Receiver[T]) {
c := make(chan T)
d := make(chan bool)
s := &Sender[T]{values: c, done: d}
r := &Receiver[T]{values: c, done: d}
// The finalizer on the receiver will tell the sender
// if the receiver stops listening.
runtime.SetFinalizer(r, r.finalize)
return s, r
}
// A Sender is used to send values to a Receiver.
type Sender[T any] struct {
values chan<- T
done <-chan bool
}
// Send sends a value to the receiver. It reports whether any more
// values may be sent; if it returns false the value was not sent.
func (s *Sender[T]) Send(v T) bool {
select {
case s.values <- v:
return true
case <-s.done:
// The receiver has stopped listening.
return false
}
}
// Close tells the receiver that no more values will arrive.
// After Close is called, the Sender may no longer be used.
func (s *Sender[T]) Close() {
close(s.values)
}
// A Receiver receives values from a Sender.
type Receiver[T any] struct {
values <-chan T
done chan<- bool
}
// Next returns the next value from the channel. The bool result
// reports whether the value is valid. If the value is not valid, the
// Sender has been closed and no more values will be received.
func (r *Receiver[T]) Next() (T, bool) {
v, ok := <-r.values
return v, ok
}
// finalize is a finalizer for the receiver.
// It tells the sender that the receiver has stopped listening.
func (r *Receiver[T]) finalize() {
close(r.done)
}
```
There is an example of using this function in the next section.
### Containers
One of the frequent requests for generics in Go is the ability to
write compile-time type-safe containers.
This design makes it easy to write a compile-time type-safe wrapper
around an existing container; we won't write out an example for that.
This design also makes it easy to write a compile-time type-safe
container that does not use boxing.
Here is an example of an ordered map implemented as a binary tree.
The details of how it works are not too important.
The important points are:
* The code is written in a natural Go style, using the key and value
types where needed.
* The keys and values are stored directly in the nodes of the tree,
not using pointers and not boxed as interface values.
```
// Package orderedmaps provides an ordered map, implemented as a binary tree.
package orderedmaps
import "chans"
// Map is an ordered map.
type Map[K, V any] struct {
root *node[K, V]
compare func(K, K) int
}
// node is the type of a node in the binary tree.
type node[K, V any] struct {
k K
v V
left, right *node[K, V]
}
// New returns a new map.
// Since the type parameter V is only used for the result,
// type inference does not work, and calls to New must always
// pass explicit type arguments.
func New[K, V any](compare func(K, K) int) *Map[K, V] {
return &Map[K, V]{compare: compare}
}
// find looks up k in the map, and returns either a pointer
// to the node holding k, or a pointer to the location where
// such a node would go.
func (m *Map[K, V]) find(k K) **node[K, V] {
pn := &m.root
for *pn != nil {
switch cmp := m.compare(k, (*pn).k); {
case cmp < 0:
pn = &(*pn).left
case cmp > 0:
pn = &(*pn).right
default:
return pn
}
}
return pn
}
// Insert inserts a new key/value into the map.
// If the key is already present, the value is replaced.
// Reports whether this is a new key.
func (m *Map[K, V]) Insert(k K, v V) bool {
pn := m.find(k)
if *pn != nil {
(*pn).v = v
return false
}
*pn = &node[K, V]{k: k, v: v}
return true
}
// Find returns the value associated with a key, or zero if not present.
// The bool result reports whether the key was found.
func (m *Map[K, V]) Find(k K) (V, bool) {
pn := m.find(k)
if *pn == nil {
var zero V // see the discussion of zero values, above
return zero, false
}
return (*pn).v, true
}
// keyValue is a pair of key and value used when iterating.
type keyValue[K, V any] struct {
k K
v V
}
// InOrder returns an iterator that does an in-order traversal of the map.
func (m *Map[K, V]) InOrder() *Iterator[K, V] {
type kv = keyValue[K, V] // convenient shorthand
sender, receiver := chans.Ranger[kv]()
var f func(*node[K, V]) bool
f = func(n *node[K, V]) bool {
if n == nil {
return true
}
// Stop sending values if sender.Send returns false,
// meaning that nothing is listening at the receiver end.
return f(n.left) &&
sender.Send(kv{n.k, n.v}) &&
f(n.right)
}
go func() {
f(m.root)
sender.Close()
}()
return &Iterator[K, V]{receiver}
}
// Iterator is used to iterate over the map.
type Iterator[K, V any] struct {
r *chans.Receiver[keyValue[K, V]]
}
// Next returns the next key and value pair. The bool result reports
// whether the values are valid. If the values are not valid, we have
// reached the end.
func (it *Iterator[K, V]) Next() (K, V, bool) {
kv, ok := it.r.Next()
return kv.k, kv.v, ok
}
```
This is what it looks like to use this package:
```
import "container/orderedmaps"
// Set m to an ordered map from string to string,
// using strings.Compare as the comparison function.
var m = orderedmaps.New[string, string](strings.Compare)
// Add adds the pair a, b to m.
func Add(a, b string) {
m.Insert(a, b)
}
```
### Append
The predeclared `append` function exists to replace the boilerplate
otherwise required to grow a slice.
Before `append` was added to the language, there was a function `Add`
in the bytes package:
```
// Add appends the contents of t to the end of s and returns the result.
// If s has enough capacity, it is extended in place; otherwise a
// new array is allocated and returned.
func Add(s, t []byte) []byte
```
`Add` appended two `[]byte` values together, returning a new slice.
That was fine for `[]byte`, but if you had a slice of some other
type, you had to write essentially the same code to append more
values.
If this design were available back then, perhaps we would not have
added `append` to the language.
Instead, we could write something like this:
```
// Package slices implements various slice algorithms.
package slices
// Append appends the contents of t to the end of s and returns the result.
// If s has enough capacity, it is extended in place; otherwise a
// new array is allocated and returned.
func Append[T any](s []T, t ...T) []T {
lens := len(s)
tot := lens + len(t)
if tot < 0 {
panic("Append: cap out of range")
}
if tot > cap(s) {
news := make([]T, tot, tot + tot/2)
copy(news, s)
s = news
}
s = s[:tot]
copy(s[lens:], t)
return s
}
```
That example uses the predeclared `copy` function, but that's OK, we
can write that one too:
```
// Copy copies values from t to s, stopping when either slice is
// full, returning the number of values copied.
func Copy[T any](s, t []T) int {
i := 0
for ; i < len(s) && i < len(t); i++ {
s[i] = t[i]
}
return i
}
```
These functions can be used as one would expect:
```
s := slices.Append([]int{1, 2, 3}, 4, 5, 6)
// Now s is []int{1, 2, 3, 4, 5, 6}.
slices.Copy(s[3:], []int{7, 8, 9})
// Now s is []int{1, 2, 3, 7, 8, 9}
```
This code doesn't implement the special case of appending or copying a
`string` to a `[]byte`, and it's unlikely to be as efficient as the
implementation of the predeclared function.
Still, this example shows that using this design would permit `append`
and `copy` to be written generically, once, without requiring any
additional special language features.
### Metrics
In a [Go experience
report](https://medium.com/@sameer_74231/go-experience-report-for-generics-google-metrics-api-b019d597aaa4)
Sameer Ajmani describes a metrics implementation.
Each metric has a value and one or more fields.
The fields have different types.
Defining a metric requires specifying the types of the fields.
The `Add` method takes the field types as arguments, and records an
instance of that set of fields.
The C++ implementation uses a variadic template.
The Java implementation includes the number of fields in the name of
the type.
Both the C++ and Java implementations provide compile-time type-safe
Add methods.
Here is how to use this design to provide similar functionality in
Go with a compile-time type-safe `Add` method.
Because there is no support for a variadic number of type arguments,
we must use different names for a different number of arguments, as in
Java.
This implementation only works for comparable types.
A more complex implementation could accept a comparison function to
work with arbitrary types.
```
// Package metrics provides a general mechanism for accumulating
// metrics of different values.
package metrics
import "sync"
// Metric1 accumulates metrics of a single value.
type Metric1[T comparable] struct {
mu sync.Mutex
m map[T]int
}
// Add adds an instance of a value.
func (m *Metric1[T]) Add(v T) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[T]int)
}
m.m[v]++
}
// key2 is an internal type used by Metric2.
type key2[T1, T2 comparable] struct {
f1 T1
f2 T2
}
// Metric2 accumulates metrics of pairs of values.
type Metric2[T1, T2 comparable] struct {
mu sync.Mutex
m map[key2[T1, T2]]int
}
// Add adds an instance of a value pair.
func (m *Metric2[T1, T2]) Add(v1 T1, v2 T2) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[key2[T1, T2]]int)
}
m.m[key2[T1, T2]{v1, v2}]++
}
// key3 is an internal type used by Metric3.
type key3[T1, T2, T3 comparable] struct {
f1 T1
f2 T2
f3 T3
}
// Metric3 accumulates metrics of triples of values.
type Metric3[T1, T2, T3 comparable] struct {
mu sync.Mutex
m map[key3[T1, T2, T3]]int
}
// Add adds an instance of a value triplet.
func (m *Metric3[T1, T2, T3]) Add(v1 T1, v2 T2, v3 T3) {
m.mu.Lock()
defer m.mu.Unlock()
if m.m == nil {
m.m = make(map[key3[T1, T2, T3]]int)
}
m.m[key3[T1, T2, T3]{v1, v2, v3}]++
}
// Repeat for the maximum number of permitted arguments.
```
Using this package looks like this:
```
import "metrics"
var m = metrics.Metric2[string, int]{}
func F(s string, i int) {
m.Add(s, i) // this call is type checked at compile time
}
```
This implementation has a certain amount of repetition due to the lack
of support for variadic type parameters.
Using the package, though, is easy and type safe.
### List transform
While slices are efficient and easy to use, there are occasional cases
where a linked list is appropriate.
This example primarily shows transforming a linked list of one type to
another type, as an example of using different instantiations of the
same generic type.
```
// Package lists provides a linked list of any type.
package lists
// List is a linked list.
type List[T any] struct {
head, tail *element[T]
}
// An element is an entry in a linked list.
type element[T any] struct {
next *element[T]
val T
}
// Push pushes an element to the end of the list.
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
// Iterator ranges over a list.
type Iterator[T any] struct {
next **element[T]
}
// Range returns an Iterator starting at the head of the list.
func (lst *List[T]) Range() *Iterator[T] {
return &Iterator[T]{next: &lst.head}
}
// Next advances the iterator.
// It reports whether there are more elements.
func (it *Iterator[T]) Next() bool {
if *it.next == nil {
return false
}
it.next = &(*it.next).next
return true
}
// Val returns the value of the current element.
// The bool result reports whether the value is valid.
func (it *Iterator[T]) Val() (T, bool) {
if *it.next == nil {
var zero T
return zero, false
}
return (*it.next).val, true
}
// Transform runs a transform function on a list returning a new list.
func Transform[T1, T2 any](lst *List[T1], f func(T1) T2) *List[T2] {
ret := &List[T2]{}
it := lst.Range()
for {
if v, ok := it.Val(); ok {
ret.Push(f(v))
}
if !it.Next() {
break
}
}
return ret
}
```
### Dot product
A generic dot product implementation that works for slices of any
numeric type.
```
// Numeric is a constraint that matches any numeric type.
// It would likely be in a constraints package in the standard library.
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~complex64 | ~complex128
}
// DotProduct returns the dot product of two slices.
// This panics if the two slices are not the same length.
func DotProduct[T Numeric](s1, s2 []T) T {
if len(s1) != len(s2) {
panic("DotProduct: slices of unequal length")
}
var r T
for i := range s1 {
r += s1[i] * s2[i]
}
return r
}
```
(Note: the generics implementation approach may affect whether
`DotProduct` uses FMA, and thus what the exact results are when using
floating point types.
It's not clear how much of a problem this is, or whether there is any
way to fix it.)
### Absolute difference
Compute the absolute difference between two numeric values, by using
an `Abs` method.
This uses the same `Numeric` constraint defined in the last example.
This example uses more machinery than is appropriate for the simple
case of computing the absolute difference.
It is intended to show how the common part of algorithms can be
factored into code that uses methods, where the exact definition of
the methods can vary based on the kind of type being used.
Note: the code used in this example will not work in Go 1.18.
We hope to resolve this and make it work in future releases.
```
// NumericAbs matches numeric types with an Abs method.
type NumericAbs[T any] interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~complex64 | ~complex128
Abs() T
}
// AbsDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method.
func AbsDifference[T NumericAbs[T]](a, b T) T {
d := a - b
return d.Abs()
}
```
We can define an `Abs` method appropriate for different numeric types.
```
// OrderedNumeric matches numeric types that support the < operator.
type OrderedNumeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64
}
// Complex matches the two complex types, which do not have a < operator.
type Complex interface {
~complex64 | ~complex128
}
// OrderedAbs is a helper type that defines an Abs method for
// ordered numeric types.
type OrderedAbs[T OrderedNumeric] T
func (a OrderedAbs[T]) Abs() OrderedAbs[T] {
if a < 0 {
return -a
}
return a
}
// ComplexAbs is a helper type that defines an Abs method for
// complex types.
type ComplexAbs[T Complex] T
func (a ComplexAbs[T]) Abs() ComplexAbs[T] {
d := math.Hypot(float64(real(a)), float64(imag(a)))
return ComplexAbs[T](complex(d, 0))
}
```
We can then define functions that do the work for the caller by
converting to and from the types we just defined.
```
// OrderedAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of an ordered type.
func OrderedAbsDifference[T OrderedNumeric](a, b T) T {
return T(AbsDifference(OrderedAbs[T](a), OrderedAbs[T](b)))
}
// ComplexAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of a complex type.
func ComplexAbsDifference[T Complex](a, b T) T {
return T(AbsDifference(ComplexAbs[T](a), ComplexAbs[T](b)))
}
```
It's worth noting that this design is not powerful enough to write
code like the following:
```
// This function is INVALID.
func GeneralAbsDifference[T Numeric](a, b T) T {
switch (interface{})(a).(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64:
return OrderedAbsDifference(a, b) // INVALID
case complex64, complex128:
return ComplexAbsDifference(a, b) // INVALID
}
}
```
The calls to `OrderedAbsDifference` and `ComplexAbsDifference` are
invalid, because not all the types that implement the `Numeric`
constraint can implement the `OrderedNumeric` or `Complex`
constraints.
Although the type switch means that this code would conceptually work
at run time, there is no support for writing this code at compile
time.
This is another way of expressing one of the omissions listed above:
this design does not provide for specialization.
## Acknowledgements
We'd like to thank many people on the Go team, many contributors to
the Go issue tracker, and all the people who have shared their ideas
and their feedback on earlier design drafts.
We read all of it, and we're grateful.
For this version of the proposal in particular we received detailed
feedback from Josh Bleecher-Snyder, Jon Bodner, Dave Cheney, Jaana
Dogan, Kevin Gillette, Mitchell Hashimoto, Chris Hines, Bill Kennedy,
Ayke van Laethem, Daniel Martí, Elena Morozova, Roger Peppe, and Ronna
Steinberg.
## Appendix
This appendix covers various details of the design that don't seem
significant enough to cover in earlier sections.
### Generic type aliases
A type alias may refer to a generic type, but the type alias may not
have its own parameters.
This restriction exists because it is unclear how to handle a type
alias with type parameters that have constraints.
```
type VectorAlias = Vector
```
In this case uses of the type alias will have to provide type
arguments appropriate for the generic type being aliased.
```
var v VectorAlias[int]
```
Type aliases may also refer to instantiated types.
```
type VectorInt = Vector[int]
```
### Instantiating a function
Go normally permits you to refer to a function without passing any
arguments, producing a value of function type.
You may not do this with a function that has type parameters; all type
arguments must be known at compile time.
That said, you can instantiate the function, by passing type
arguments, but you don't have to call the instantiation.
This will produce a function value with no type parameters.
```
// PrintInts is type func([]int).
var PrintInts = Print[int]
```
### Embedded type parameter
When a generic type is a struct, and the type parameter is
embedded as a field in the struct, the name of the field is the name
of the type parameter.
```
// A Lockable is a value that may be safely simultaneously accessed
// from multiple goroutines via the Get and Set methods.
type Lockable[T any] struct {
T
mu sync.Mutex
}
// Get returns the value stored in a Lockable.
func (l *Lockable[T]) Get() T {
l.mu.Lock()
defer l.mu.Unlock()
return l.T
}
// Set sets the value in a Lockable.
func (l *Lockable[T]) Set(v T) {
l.mu.Lock()
defer l.mu.Unlock()
l.T = v
}
```
### Embedded type parameter methods
When a generic type is a struct, and the type parameter is embedded as
a field in the struct, any methods of the type parameter's constraint
are promoted to be methods of the struct.
(For purposes of [selector
resolution](https://golang.org/ref/spec#Selectors), these methods are
treated as being at depth 0 of the type parameter, even if in the
actual type argument the methods were themselves promoted from an
embedded type.)
```
// NamedInt is an int with a name. The name can be any type with
// a String method.
type NamedInt[Name fmt.Stringer] struct {
Name
val int
}
// Name returns the name of a NamedInt.
func (ni NamedInt[Name]) Name() string {
// The String method is promoted from the embedded Name.
return ni.String()
}
```
### Embedded instantiated type
When embedding an instantiated type, the name of the field is the name
of type without the type arguments.
```
type S struct {
T[int] // field name is T
}
func F(v S) int {
return v.T // not v.T[int]
}
```
### Generic types as type switch cases
A generic type may be used as the type in a type assertion or as a
case in a type switch.
Here are some trivial examples:
```
func Assertion[T any](v interface{}) (T, bool) {
t, ok := v.(T)
return t, ok
}
func Switch[T any](v interface{}) (T, bool) {
switch v := v.(type) {
case T:
return v, true
default:
var zero T
return zero, false
}
}
```
In a type switch, it's OK if a generic type turns out to duplicate
some other case in the type switch.
The first matching case is chosen.
```
func Switch2[T any](v interface{}) int {
switch v.(type) {
case T:
return 0
case string:
return 1
default:
return 2
}
}
// S2a will be set to 0.
var S2a = Switch2[string]("a string")
// S2b will be set to 1.
var S2b = Switch2[int]("another string")
```
### Method sets of constraint elements
Much as the type set of an interface type is the intersection of the
type sets of the elements of the interface, the method set of an
interface type can be defined as the union of the method sets of the
elements of the interface.
In most cases, an embedded element will have no methods, and as such
will not contribute any methods to the interface type.
That said, for completeness, we'll note that the method set of `~T` is
the method set of `T`.
The method set of a union element is the intersection of the method
sets of the elements of the union.
These rules are implied by the definition of type sets, but they are
not needed for understanding the behavior of constraints.
### Permitting constraints as ordinary interface types
This is a feature we are not suggesting now, but could consider for
later versions of the language.
We have proposed that constraints can embed some additional elements.
With this proposal, any interface type that embeds anything other than
an interface type can only be used as a constraint or as an embedded
element in another constraint.
A natural next step would be to permit using interface types that
embed any type, or that embed these new elements, as an ordinary type,
not just as a constraint.
We are not proposing that now.
But the rules for type sets and method sets above describe how they
would behave.
Any type that is an element of the type set could be assigned to such
an interface type.
A value of such an interface type would permit calling any member of
the method set.
This would permit a version of what other languages call sum types or
union types.
It would be a Go interface type to which only specific types could be
assigned.
Such an interface type could still take the value `nil`, of course, so
it would not be quite the same as a typical sum type as found in other
languages.
Another natural next step would be to permit approximation elements
and union elements in type switch cases.
That would make it easier to determine the contents of an interface
type that used those elements.
That said, approximation elements and union elements are not types,
and as such could not be used in type assertions.
### Type inference for composite literals
This is a feature we are not suggesting now, but could consider for
later versions of the language.
We could also consider supporting type inference for composite
literals of generic types.
```
type Pair[T any] struct { f1, f2 T }
var V = Pair{1, 2} // inferred as Pair[int]{1, 2}
```
It's not clear how often this will arise in real code.
### Type inference for generic function arguments
This is a feature we are not suggesting now, but could consider for
later versions of the language.
In the following example, consider the call to `Find` in `FindClose`.
Type inference can determine that the type argument to `Find` is `T4`,
and from that we know that the type of the final argument must be
`func(T4, T4) bool`, and from that we could deduce that the type
argument to `IsClose` must also be `T4`.
However, the type inference algorithm described earlier cannot do
that, so we must explicitly write `IsClose[T4]`.
This may seem esoteric at first, but it comes up when passing generic
functions to generic `Map` and `Filter` functions.
```
// Differ has a Diff method that returns how different a value is.
type Differ[T1 any] interface {
Diff(T1) int
}
// IsClose returns whether a and b are close together, based on Diff.
func IsClose[T2 Differ](a, b T2) bool {
return a.Diff(b) < 2
}
// Find returns the index of the first element in s that matches e,
// based on the cmp function. It returns -1 if no element matches.
func Find[T3 any](s []T3, e T3, cmp func(a, b T3) bool) int {
for i, v := range s {
if cmp(v, e) {
return i
}
}
return -1
}
// FindClose returns the index of the first element in s that is
// close to e, based on IsClose.
func FindClose[T4 Differ](s []T4, e T4) int {
// With the current type inference algorithm we have to
// explicitly write IsClose[T4] here, although it
// is the only type argument we could possibly use.
return Find(s, e, IsClose[T4])
}
```
### Reflection on type arguments
Although we don't suggest changing the reflect package, one
possibility to consider for the future would be to add two new
methods to `reflect.Type`: `NumTypeArgument() int` would return the
number of type arguments to a type, and `TypeArgument(i) Type` would
return the i'th type argument.
`NumTypeArgument` would return non-zero for an instantiated generic
type.
Similar methods could be defined for `reflect.Value`, for which
`NumTypeArgument` would return non-zero for an instantiated generic
function.
There might be programs that care about this information.