Status

This is the design for adding generic programming using type parameters to the Go language. This design has been proposed and accepted as a future language change. We currently expect that this change will be available in the Go 1.18 release in early 2022.

这是在Go语言中加入使用Type Parameter的泛型编程的设计。 这个设计已经被提出并接受为未来的语言变化。 我们目前预计,这一变化将在2022年初的Go 1.18版本中出现。

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.

我们建议扩展Go语言,在类型和函数声明中增加可选的type parameter

type parameter受到接口类型的约束。

接口类型作为类型约束时,支持嵌入额外的元素,可用于限制满足约束的类型集。

参数化的类型和函数可以使用带有type parameter的操作符,但只有在满足参数约束的所有类型允许的情况下。

通过统一算法进行的类型推断允许在许多情况下从函数调用中省略type parameter

该设计完全向后兼容 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.

这一节非常简要地解释了泛型在Go语言中的设计,是为已经有在某些语言中有使用泛型的经验的人而准备的。

这些概念将在下面的章节中详细解释。

  • 函数可以有一个额外的type parameter列表,它使用方括号,但其他方面看起来像一个普通的参数列表 func F[T any](p T) { ... }
  • 这些类型的参数可以由常规参数和在函数体中使用。
  • 类型也可以有一个type parameter列表:type M[T any] []T
  • 每个type parameter都有一个类型约束,就像每个普通参数都有一个类型一样:func F[T Constraint](p T) { ... }
  • 类型约束是接口类型。
  • 将会预置一个新的类型any, 是允许任何类型的类型约束。
  • 用作类型约束的接口类型可以嵌入额外的元素来限制满足约束的type arguments集:
    • 任意类型 T 限制为该类型。
    • 一个近似元素~T限制了底层类型为T的所有类型。
    • 也可以使用组合元素 T1 | T2 | ...,限制到所列的任何元素。
  • Generic Functions 只能使用约束条件所允许的所有类型所支持的操作。
  • 使用Generic Functions或类型需要传递type arguments
  • 类型推断允许在常见情况下省略函数调用的type arguments

Background

There have been many requests to add additional support for generic programming in Go. There has been extensive discussion on the issue tracker and on a living document.

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.

有很多人要求在Go中增加对通用编程的额外支持。这些功能在the issue trackera living document 中进行了广泛的讨论。

某些人建议对Go语言进行扩展,增加一种参数化的多态性,其中type parameters不是由已声明的子类型关系(如一些面向对象的语言)来约束,而是由明确定义的结构约束。这个版本的设计与2019年7月31日提交的设计草案有许多相似之处,但contracts已被删除,由接口类型取代,而且语法也有变化。

已经有几个关于增加type parameters的建议,可以通过上面的链接找到。这里提出的许多想法以前就出现过。这里描述的主要新特征是语法和对接口类型作为约束条件的仔细检查。

这种设计不支持模板元编程或任何其他形式的编译时编程。~~~哈哈哈(^.^)

由于泛型一词在Go社区中被广泛使用,我们将在下文中把它作为一个速记符号,指的是一个接受type parameters的函数或类型。不要把本设计中使用的泛型一词与其他语言如C++C#JavaRust中的相同术语混淆,它们有相似之处,但并不一样。 ~~~哈哈哈哈(^.^)

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.

泛型代码是使用抽象的数据类型编写的,我们称之为type parameters

When running the generic code, the type parameters are replaced by type arguments.

当运行泛型代码时,type parameterstype arguments所取代。

// 原文没有 下面好多 type parameters 与 type arguments 别搞混了 
Print[T any](s []T)  // T is Parameters
Print[int]([]int{1, 2, 3}) // int is 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).

这是一个打印出切片的每个元素的函数,其中切片的元素类型 T , 然而它是未知的类型。 这是我们为了支持泛型编程而希望允许的那种函数的一个简单示例。(稍后我们将会讨论 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.

采用这种方法,首先要做的决定是:应该如何声明type parameters T

在像Go这样的语言中,我们希望每个标识符都能以某种方式被声明

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.

这里我们做了一个设计决定:type parameters与普通的非类型函数参数相似,因此应该与其他参数一起列出。 然而,type parameters与非type parameters不一样,所以尽管它们出现在参数列表中,我们还是要将它们区分开来。 这导致了我们的下一个设计决定:我们定义了一个额外的可选参数列表来描述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.

这个type parameters列表出现在常规参数的前面。为了区分type parameters列表和常规参数列表,type parameters列表使用方括号而不是小括号。

就像常规参数有类型一样,type parameters也有元类型,也被称为约束。

我们将在后面讨论约束条件的细节;现在,我们只需注意任何是一个有效的约束条件,意味着任何类型都是允许的。

// 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.

这就是说,在函数Print中,标识符T是一个type parameters,这个类型目前是未知的,但当函数被调用时将会被知道。任何意味着T可以是任何类型。如上所述,在描述普通non-type parameters的类型时,类type parameters可以作为一个类型使用。它也可以在函数的主体中作为一个类型使用。

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.

与普通的参数列表不同,在type parameters列表中,type parameters的名字是必须的。这就避免了语法上的歧义,而且,恰好没有理由省略type parameters的名称。

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. 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.

由于 Print 有一个type parameters,因此任何 Print 调用都必须提供一个type argument

稍后我们将看到通常如何使用类型推断从非类型参数中推导出此type argument

现在,我们将显式传递type argumenttype argument的传递很像type parameters的声明:作为一个单独的参数列表。

type parameters列表一样,type argument列表使用方括号。

	// 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.

我们的例子稍微复杂一点,把它变成一个函数,通过在每个元素上调用 String 方法将任何类型的切片转换为 []string

// 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.

乍看之下这似乎没问题,但在这个例子中,vT类型,而T可以是任何类型。这意味着T不需要有一个String方法。所以对v.String()的调用是无效的。

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.

自然,同样的问题也出现在其他支持泛型编程的语言中。例如,在C++中,一个泛型函数(用C++的话来说,就是一个函数模板)可以调用泛型类型的值上的任何方法。也就是说,在C++方法中,调用v.String()是可以的。如果用一个没有String方法的type argument来调用该函数,那么在用该type argument编译调用v.String时就会报告错误。这些错误可能很冗长,因为在错误发生之前可能有几层泛型函数的调用,所有这些都必须被报告以了解出错的原因。

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.

C++ 方法对于 Go 来说不是一个好的选择, 原因之一是语言的风格。

Go 中,我们不会refer to names,例如本例中的 String,并希望它们存在。

Go 在看到它们时将所有names解析为它们(可能代指 function)的声明。

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.

另一个原因是,Go的设计是为了支持规模化的编程。我们必须考虑这样的情况:泛型函数的定义(上面的Stringify)和对泛型函数的调用(没有显示,但可能在其他包中)相距甚远。一般来说,所有的泛型代码都希望type argument能够满足某些要求。我们把这些要求称为约束(其他语言也有类似的想法,称为类型约束或特性约束或概念)。在这种情况下,约束条件是非常明显的:该类型必须有一个String()字符串方法。

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.

我们不希望从 Stringify 碰巧做的任何事情中推导出约束。如果我们这样做了,对 Stringify 的一个小改动可能会改变约束。这意味着一个微小的变化可能会导致程序别处Break, 这在具有规模的项目开发里是不被希望的。 Stringify 可以故意更改其约束并强制调用者更改。我们要避免的是 Stringify 意外更改其约束。

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.

这意味着约束必须对调用者传递的type argument和泛型函数中的代码都设置限制。调用者只能传递满足约束条件的type argumentGeneric Functions 只能以约束条件允许的方式使用这些值。这是一条重要的规则,我们认为它应该适用于任何在Go中定义泛型编程的尝试:泛型代码只能使用其type argument已经实现的接口约束的方法。

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.

在我们进一步讨论约束之前,让我们简要说明当约束为 any 时会发生什么。如果泛型函数对type parameter使用 any 约束,如上述 Print 方法的情况,则该参数允许任何type argument。泛型函数可以对该type argument的值使用的唯一操作是那些允许用于任何类型的值的操作。在上面的示例中,Print 函数声明了一个类型为type parameters T 的变量 v,并将该变量传递给一个函数。

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

对于 any 来说,它被允许的操作如下:

  • 声明这些类型的变量
  • 将相同类型的其他值分配给这些变量
  • 将这些变量传递给函数或从函数中返回这些变量
  • 获取这些变量的地址
  • 将这些类型的值转换或者分配给 interface{}
  • 使用类型断言将接口值转换为具体类型
  • type switch中 将这个类型作为 一种 case使用
  • 定义和使用使用这些类型的复合类型,例如该类型的切片
  • 将类型传递给一些预先声明的函数,例如 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.

Go 已经有一个接近于我们需要的约束的构造:接口类型。

接口类型是一组方法。

唯一可以分配给接口类型变量的值是那些其类型实现了相同方法的值。

除了任何类型允许的操作之外,可以使用接口类型的值完成的唯一操作是调用方法。

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).

使用type argument调用泛型函数类似于分配给接口类型的变量:type argument必须实现type parameters的约束。

编写泛型函数就像使用接口类型的值:泛型代码只能使用约束允许的操作(或任何类型允许的操作)。

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).

因此,在这个设计中,约束只是接口类型。满足约束意味着实现接口类型。 (稍后我们将重申这一点,以便为方法调用以外的操作定义约束,例如binary 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. 对于Stringify的例子,我们需要一个具有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.)

(这对这次讨论并不重要,但这定义了与标准库的fmt.Stringer类型相同的接口,真正的代码可能会简单地使用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

现在我们知道约束只是接口类型,我们可以解释什么是约束。如上所示,any 约束允许任何类型作为type argument,并且只允许函数使用任何类型允许的操作。其接口类型是空接口:interface{}。所以我们可以将 Print 示例写成:

// 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.

但是,每次编写不对其type parameters施加约束的泛型函数时都必须编写 interface{} 是很乏味的。 所以在这个设计中我们建议一个类型约束 any 等同于 interface{}。 这将是 Go 语言中的一个预先内置的隐式声明, 全局有效。 另外将 any 用作类型约束以外的任何内容都是无效的。

(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).

(注意:我们可以使任何代码中的变量 作为 interface{} 的别名,或作为定义为 interface{} 的新定义类型。但是,我们不希望这种关于泛型的设计可能导致对非通用代码的重大更改。将 any 添加为 interface{} 的通用名称可以而且应该单独讨论)。

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.

对于一个泛型函数,约束可以被认为是type argument的类型:a meta-type

如上所示,约束在type parameters列表中作为type parametersmeta-type出现。

// 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.

单一type parameters T 后跟适用于 T 的约束,在本例中为 Stringer

Multiple type parameters

Although the Stringify example uses only a single type parameter, functions may have multiple type parameters.

尽管 Stringify 示例仅使用单个type parameters,但函数可能具有多个type parameters

// Print2 has two type parameters and two non-type parameters.
func Print2[T1, T2 any](s1 []T1, s2 []T2) { ... }

对比下面的代码

// 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.

Print2 中,s1s2 可能是不同类型的切片。 在 Print2Same 中,s1s2 必须是相同元素类型的切片。

Just as each ordinary parameter may have its own type, each type parameter may have its own constraint.

就像每个普通参数可以有自己的类型一样,每个type parameters也可以有自己的约束条件。

// 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.

单个约束可用于多个type parameters,就像单个类型可用于多个非类型函数参数一样。该约束分别应用于每个type parameters

// 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.

一个类型的type parameters就像一个函数的type parameters一样。

Within the type definition, the type parameters may be used like any other type.

在类型定义中,type parameters可以像其他类型一样使用。

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.

要使用泛型类型,您必须提供type argument。 这称为 _instantiation_。 像往常一样,type arument出现在方括号中。 当我们通过为type paramenters提供type argument来实例化一个类型时,我们生成了一个类型,其中类型定义中的每个type parameters的使用都被相应的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.

泛型的类型可以有方法。方法的receiver类型必须声明与receiver类型定义中声明的相同数量的类型参数。它们的声明没有任何限制。

// 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.

在类型通常可以引用自身的情况下,泛型类型可以引用自身,但是当它这样做时,type arguments必须是type parameter以相同的顺序列出。此限制可防止类型实例化的无限递归。

// 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] (0.0) 注意这里
}

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.) (注意:随着社区的深入讨论,我们对Gopher写代码的方式有了更多的了解,也许可以放宽这个规则,允许一些使用不同type argument的情况)。

The type parameter of a generic type may have constraints other than any.

泛型类型的type parameters可能具有除任何约束之外的约束。

// 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.

虽然泛型类型的方法可以使用类型的参数,但方法本身可能没有额外的type's parameters。在向方法添加type parameters有用的地方,人们将不得不编写一个适当参数化的顶级函数。 在the issues section对此有更多讨论。

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.

正如我们所见,我们使用接口类型作为约束。接口类型提供一组方法,仅此而已。 这意味着,就我们目前所见,泛型函数唯一可以对type parameters值执行的操作是调用方法,而不是允许对任何类型执行的操作。

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—an interface type—that permits <.

任何合理的泛型实现都应该让您编写此函数。问题是表达式 v < r。 这假定 T支持 < 运算符,但对 T 的约束只是any。在 any 约束下,函数 Smallest 只能使用适用于所有类型的操作,但并非所有 Go 类型都支持 <。 不幸的是,由于 < 不是方法,因此没有明显的方法来编写允许 < 的约束(接口类型)。

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.

我们需要一种方法来编写一个只接受支持 < 的类型的约束。 为了做到这一点,我们观察到,除了我们稍后将讨论的两个例外,语言定义的所有算术、比较和逻辑运算符只能与语言预先声明的类型或定义的类型一起使用其基础类型是那些预先声明的类型之一。 也就是说,运算符 < 只能与预先声明的类型(例如 intfloat64)一起使用,或者与基础类型是这些类型之一的已定义类型一起使用。 Go 不允许将 < 与复合类型或任意定义的类型一起使用。

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 set来做到这一点。

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.

每个类型都有一个相关的类型集。一个非接口类型T的类型集只是{T}的集合:一个只包含T本身的集合。一个普通接口类型的类型集是所有声明了该接口所有方法的类型的集合。

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.

请注意,一个普通接口类型的类型集是一个无限的集合。对于任何给定的类型Tinteface type IT,很容易知道T是否在IT的类型集中(通过检查IT的所有方法是否由T声明),但是没有合理的方法来列举IT类型集中的所有类型。IT的类型是它自己的类型集的成员,因为一个接口本来就声明了它自己的所有方法。空接口interface{}的类型集是所有可能类型的集合。

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.

通过查看接口的元素来构造接口类型的类型集将很有用。

这将以不同的方式产生相同的结果。

接口的元素可以是方法签名或嵌入式接口类型。

虽然方法签名不是类型,但为它定义一个类型集很方便:声明该方法的所有类型的集合。

嵌入式接口类型 E 的类型集就是 E 的类型集:声明 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 }.

对于任何方法签名Minterface{ M }的类型集是M的类型:声明M的所有类型的集合。

对于任何方法签名M1M2interface{ M1; M2 }的类型集是同时声明M1M2的所有类型的集合。这就是M1的类型集和M2的类型集的交集。

要看到这一点,请注意,M1的类型集是所有具有M1方法的类型的集合,同样,M2也是如此。

如果我们取这两个类型集的交集,结果就是同时声明M1M2的所有类型的集合。这正是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.

这同样适用于嵌入式接口类型。对于任何两个接口类型E1E2interface{ E1; E2 }的类型集是E1E2的类型集的交集。

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.

现在我们已经描述了接口类型的类型集,我们将重新定义满足约束的含义。 之前我们说过,如果type argument实现了约束,那么它就满足约束。 现在我们说如果type argument是约束类型集的成员,则它满足约束。

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.

第一个新元素是简单地允许列出任何类型,而不仅仅是一个接口类型。例如:type Integer interface{ int }。当一个非接口类型T被列为一个约束的元素时,它的类型集只是{T}int的类型集是{int}。由于约束的类型集是所有元素的类型集的交集,所以Integer的类型集也是{int}。这个约束Integer可以被任何属于集合{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.

类型可以是指一个type parameter(或不止一个)的类型字面量,但它不可以是一个普通的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.

列出一个单一的类型本身是没有用的。为了满足约束条件,我们希望不仅能说int,而且能说 “任何底层类型为int的类型”。考虑一下上面的Smallest的例子。我们希望它不仅对预先声明的有序类型的片断起作用,而且对程序定义的类型也起作用。如果一个程序使用MyString类型的字符串,该程序可以使用<操作符与MyString类型的值, 并且可以用MyString类型来实例化Smallest

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.

为了支持这一点,我们在约束中允许的第二个新元素是一个新的语法结构:一个近似元素,写成~T。~T的类型集是其基本类型为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.

例如: ype AnyString interface{ ~string } 类型集是 ~string, 因此 AnyString 的类型集 的底层类型是 string, 这包括MyString类型;作为类型参数的MyString将满足AnyString的约束。

This new ~T syntax will be the first use of ~ as a token in Go.

这个新的 ~T语法将是 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).

因为~T是指底层类型为T的所有类型的集合,所以对底层类型不是自己的类型T使用~T将是一个错误。底层类型为其自身的类型是: 1. 字面类型,例如: []byte 或者 struct {f int}。 2. 大多数预先声明的类型,如int或string(但不包含error)。

Using ~T is not permitted if T is a type parameter or if T is an interface type.

如果 Ttype parametersTinterface type,则不允许使用 ~T

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:

我们在约束中允许的第三个新元素也是一个新的语法结构:联合元素,写成一系列的约束元素,用竖条|隔开。例如:int | float32~int8 | ~int16 | ~int32 | ~int64。一个联合元素的类型集是序列中每个元素的类型集的联合。在一个联合中列出的元素必须都是不同的。比如说

// 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:

这个 union 元素的类型集是集合 {int, int8, int16, int32, int64}。由于int | int8 | int16 | int32 | int64PredeclaredSignedInteger 的唯一元素,因此它也是 PredeclaredSignedInteger 的类型集。这五种类型中的任何一种都可以满足此约束。 下面是一个例子:

// 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

此约束的类型集是其基础类型为 int、int8、int16、int32 或 int64 之一的所有类型的集合。这些类型中的任何一种都将满足此约束。 新的约束元素语法是:

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.

类型集的目的是允许泛型函数对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; 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.

规则是泛型函数可以使用参数约束类型集中每个成员允许的任何方式使用其类型为type parameter的值。 这适用于“<”或“+”等运算符或其他通用运算符。 对于像range loop这样的特殊用途运算符,如果type parameter具有结构约束(如后文定义),我们允许使用它们;这里的定义基本上是约束具有单一的基础类型。 如果函数可以使用约束类型集中的每种类型成功编译,或者在适用时使用结构类型,则允许使用。

对于前面显示的最小的例子,我们可以使用这样的约束:

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:

前面我们提到过运算符只能与语言预先声明的类型一起使用的规则有两个例外。 例外情况是 ==!=,它们允许用于structarrayinterace types。 这非常有用,以至于我们希望能够编写一个接受任何可比较类型的约束。 为此,我们引入了一个新的预声明类型约束:comparable。 可比较约束的类型集是所有可比较类型的集合。 这允许将 ==!= 与该type parameter的值一起使用。 例如,这个函数可以用任何可比较的类型实例化:

// 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.

由于comparable是一种约束,它可以被嵌入到另一个用作约束的接口类型中。

// 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.

ComparableHasher约束是由任何可比较的类型实现的,并且也有一个Hash() uintptr方法。 一个使用ComparableHasher作为约束的通用函数可以比较该类型的值,并且可以调用Hash方法。

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.

有可能使用comparable来产生一个不能被任何类型满足的约束。也请看下面关于空类型集的讨论

// 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.)

在一个单一的type parameter列表中,约束条件可以引用任何其他的type parameter,甚至是在同一列表中稍后声明的参数。 (一个type parameter的范围从type parameter列表的开头开始,一直延伸到包围函数或类型声明的结尾)。

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.

例如,考虑一个通用图包,它包含了处理图的通用算法。这些算法使用两种类型:NodeEdgeNode应该有一个方法Edges() []EdgeEdge应该有一个方法Nodes() (Node, Node)。一个图可以表示为一个[]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.

这里有很多type argument被实例化在这。在 GraphNode 的约束中,传递给类型约束 NodeConstraintEdgeGraph 的第二个type parameter。 这用type parameter Edge 实例化了 NodeConstraint,因此我们看到 Node 必须有一个返回 Edge 切片的方法 Edges,这正是我们想要的。这同样适用于对 Edge 的约束,并且对函数 New 重复相同类型的参数和约束。我们并不是说这很简单,而是说这是可能的。

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:

值得注意的是,虽然乍一看这可能看起来像是接口类型的典型用法,但 NodeEdge 是具有特定方法的非接口类型。 为了使用 graph.Graph,用于 NodeEdgetype argument必须定义遵循特定模式的方法,但它们不必实际使用接口类型来这样做。特别是,这些方法不返回接口类型。

例如,考虑其他包中的这些类型定义:

// 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.

这里没有接口类型,但我们可以使用类型参数*Vertex*FromTo来实例化Graph.Graph

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.

*Vertex*FromTo 不是接口类型,但一起使用时它们定义了实现 graph.Graph 约束的方法。请注意,我们不能将普通的 VertexFromTo 传递给 graph.New,因为 VertexFromTo 没有实现约束。 EdgesNodes 方法定义在指针类型*Vertex*FromTo 上; VertexFromTo 类型没有任何方法。

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.

当我们使用泛型接口类型作为约束时,我们首先使用type parameter list列表中提供的type argument(s)实例化该类型,然后将相应的类型参数与实例化的约束进行比较

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.

在这个例子中,graph.NewNode约束是NodeConstraint[Edge]。 当我们用*Vertex*FromTo调用graph.New时,为了检查Node的约束,编译器用*FromTo实例化NodeConstraint。 这就产生了一个实例化的约束,在这种情况下,要求Node有一个Edges()[]*FromTo的方法,编译器会验证*Vertex是否满足该约束。

Although Node and Edge do not have to be instantiated with interface types, it is also OK to use interface types if you like.

虽然Node和Edge不一定要用接口类型来实例化,但如果你喜欢,使用接口类型也是可以的。

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.

我们可以用 NodeInterfaceEdgeInterface 类型实例化 graph.Graph,因为它们实现了类型约束。没有太多理由以这种方式实例化类型,但它是被允许的。

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 parameters引用其他type parameters的能力说明了一个重要的观点:任何向 Go 中添加泛型特性的尝试,都应该满足能够使用多个type arguments实例化泛型代码,这些type arguments 每个都可以被编译器检查。(基调 0.0 )

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:

在多数情况下,我们可以使用类型推断来避免必须显式写出部分或全部type arguments

我们可以_function argument type inference_,从non-type arguments的类型中推断出type arguments

我们可以使用_constraint type inference_, 从已知type arguments中推导出未知type arguments

在上面的示例中,在实例化泛型函数或类型时,我们总是为所有type parmameters指定type arguments。当可以推断缺少的type arguments时,我们还允许仅指定一些type arguments,或完全省略type arguments

当只传递一些type arguments时,它们是列表中第一个type parameters的参数。

例如,像这样的函数:

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.)

可以通过这些方式调用。(我们将在下面详细解释类型推理是如何工作的;这个例子是为了说明如何处理一个不完整的type arguments列表)。

	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.

如果在没有指定所有type arguments的情况下使用泛型函数或类型,如果任何未指定的type arguments不能被推断出来,那就是一个错误。

(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_Type unification适用于两类, 其中一个或两个都可能是包含type parmameters

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.

Type unification是通过比较类型的结构来实现的。 不考虑type parmameters的它们的结构必须是相同的,除type parmameters以外的类型必须是等价的。 一个类型中的type parmameters可以匹配另一个类型中的任何完整的子类型。

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).

如果结构不同,或者type parmameters以外的类型不等价,则Type unification失败。成功的Type unification提供了类型参数与其他类型(它们本身可能是或包含type parmameters)的关联列表。

For type unification, two types that don’t contain any type parameters are equivalent if they are identical, 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.

对于Type unification,如果两个不包含任何type parmameters的类型是相同的,或者如果它们是忽略channel方向的channel type是相同的,或者如果它们的底层类型是等价的。在类型推理过程中,允许类型不完全相同是可以的,因为如果推理成功,我们仍然会检查约束条件,而且我们仍然会检查函数参数是否可以分配给推理的类型。

For example, if T1 and T2 are type parameters, []map[int]bool can be unified with any of the following:

例如,如果 T1T2type parameters,则 []map[int]bool 可以与以下任何一个统一:

  • []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.)

(当然应该还有跟多的例子可以匹配上述情况)

举个反面的栗子,[]map[int]bool 不能匹配到下面情况 * 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.

通常我们也可以在两侧都有type parmameters,因此在某些情况下我们可能会将 T1 与例如 T2 或 []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 of a call to the simple Print function:

函数参数类型推断与函数调用一起使用,从non-type argument中推断出type argument。当一个类型被实例化时,不使用函数参数类型推理,当一个函数被实例化但没有被调用时,也不使用。

为了看看它是如何工作的,让我们回到调用简单的Print函数的例子中:

	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.

这个函数调用中的 type argument int可以从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.

唯一可以推断的type argument是那些用于函数输入参数的类型。 如果有一些type parameters只用于函数的结果参数类型,或者只在函数的主体中使用,那么这些type argument不能用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.

为了推断函数type argument,我们将函数调用参数的类型与函数的non-type parameters的类型统一起来。 在调用方,我们有实际(非类型)参数的类型列表,对于Print的例子,它只是[]int。 在函数一侧是函数的non-type parameters的类型列表,对于Print来说是[]T。在这些列表中,我们放弃了函数侧没有使用类型参数的各个参数。 然后我们必须对剩余的参数类型应用类型统一。

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.

函数参数类型推断是通过 two-pass algorithm来实现的。在第一遍中,我们忽略调用方的无类型常量及其在函数定义中的相应类型。我们使用两次传递,以便在某些情况下后面的参数可以确定无类型常量的类型。

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.

我们在列表中统一了相应的类型。这将给我们提供一个函数侧的type parameters与调用者侧的类型的关联。如果同一个类型的参数在函数端出现多次,它将与调用方的多个参数类型相匹配。如果这些调用方的类型不对等,我们会报告一个错误。

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.

在第一遍之后,我们检查调用方的任何未类型化的常量。如果没有未定型的常量,或者相应的函数类型中的type parameters已经与其他输入类型相匹配,那么类型统一就完成了。

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. 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.

当约束类型推断是可能的时,如下所述,它被应用在两个过程之间。

看看栗子

	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].

我们将 []int[]T 进行比较,将 Tint 进行匹配,然后我们就完成了。单个type parameters Tint,因此我们推断对 Print 的调用实际上是对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

两个type parameters FT 都用于输入参数,因此函数参数类型推断是可能的。看看下面的函数调用

	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].

我们统一 []int[]F,匹配 Fint。我们将strconv.Itoa的类型统一为func(int) string,与func(F) T匹配,F匹配intT匹配stringtype parametersF 被匹配了两次,两次都是int。统一成功,所以写成Map的调用就是对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).

在调用 NewPair(1, 2) 时,两个参数都是无类型常量,因此在第一遍中都将被忽略。没有什么可以统一的。第一次通过后,我们仍然有两个无类型常量。两者都设置为其默认类型 int。类型统一过程的第二次运行将 Fint 统一,因此最终调用是 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)).

NewPair(1, int64(2)) 调用中,第一个参数是一个无类型常量,所以我们在第一遍中忽略它。然后我们将int64F统一起来,此时无类型常量对应的type parameters就完全确定了,所以最后调用的是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.

NewPair(1, 2.5) 调用中,两个参数都是无类型常量,所以我们继续第二遍。这次我们将第一个常量设置为 int,将第二个常量设置为float64。然后我们尝试将 Fintfloat64 统一,因此统一失败,并报告编译错误。

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.

如前所述,函数参数类型推理是在不考虑约束条件的情况下进行的。首先,我们使用函数参数类型推理来确定函数要使用的type parameters,然后,如果成功了,我们检查这些type parameters是否实现了约束条件(如果有的话)。 注意,在成功的函数参数类型推理之后,编译器仍然必须检查参数是否可以被分配给参数,就像任何函数调用一样。

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.

约束类型推断允许根据type parameter约束从另一个type argument推断type argument。 当一个函数想要为某个其他type parameter的元素使用类型名称,或者当一个函数想要将约束应用于基于某个其他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.

约束类型推理只有在某些type parameter 有一个约束,该约束有一个类型集,其中正好有一个类型,或者一个 类型集中每个类型的基本类型都是同一类型。 是同一类型。

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.

我们首先创建一个从类型参数到类型参数的映射。我们用已经知道类型参数的type arguments来初始化该映射,如果有的话。

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.

对于每个具有结构约束的type parameter,我们将类型参数与结构类型统一起来。 这样可以讲类型参数与约束相关联。我们将结果添加到我们正在维护的映射中。如果统一发现类型参数的任何关联,我们也将它们添加到映射中。 当我们找到任何一种类型参数的多个关联时,我们统一每个这样的关联以生成一个映射条目。如果一个类型参数直接与另一个类型参数相关联,这意味着它们必须与同一类型匹配,我们将每个参数的关联统一在一起。如果这些不同的统一中的任何一个失败,则约束类型推断失败。

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.

我们继续寻找映射到完全已知type argument Atype parameter T,该类型参数不包含任何类型参数。

Anywhere that T appears in a type argument in the mapping, we replace T with A.

在映射中 T 出现在type argument中的地方,我们用 A 替换 T

We repeat this process until we have replaced every type parameter.

我们重复这个过程,直到我们替换了每个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.

当可以进行约束类型推断时,类型推断如下进行: * 使用已知type arguments构建映射。 * 使用约束类型推断。 * 用 type arguments 来进行 function type inference * 使用约束类型推断。 * 将剩下未知类型使用默认的方式来进行函数类型推断 * 使用约束类型推断。

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 requirement.

对于约束类型推断有用的示例,让我们考虑一个函数,该函数采用作为数字切片的定义类型,并返回相同定义类型的实例,其中每个数字都加倍。 如果我们忽略defined type要求,就很容易编写类似这样的函数。

// 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.

函数参数类型推断本身不足以推断此处的类型参数,因为类型参数 E 不用于任何输入参数。 但是函数参数类型推断和约束类型推断的组合有效。

// 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:

首先我们应用函数参数类型推断。我们看到参数的类型是 MySlice。函数参数类型推断将type parameter SMySlice 相匹配。 然后我们继续进行约束类型推断。我们知道一个type argument S。我们看到type argument S 具有结构类型约束。 我们创建已知类型参数的映射:

{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:

然后,我们将每个type parameter与结构约束和该约束的类型集中的单一类型统一起来。 在这种情况下,结构约束是~[]E,其结构类型为 []E,因此我们将 S[]E 统一。 因为我们已经有了 S的映射,所以我们将 []EMySlice 统一起来。由于 MySlice 被定义为 []int, 因此将 Eint 相关联。

我们现在有:

{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.

然后我们用 int 替换 E,这没有任何改变,我们就完成了。 调用 DoubleDefinedtype arguments[MySlice, int]。 这个例子展示了我们如何使用约束类型推断来为某个其他类型参数的元素设置类型名称。 在这种情况下,我们可以将 S 的元素类型命名为 E,然后我们可以对 E 应用进一步的约束,在这种情况下要求它是一个数字。

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.

考虑这个例子,一个函数期望有一个类型T,它有一个Set(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.

目标是使用 FromStrings 获取 []Settable 类型的切片。 不幸的是,这个例子是无法通过编译器的。

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.

问题是 FromStrings 需要一个具有 Set(string) 方法的类型。 函数 F 试图用 Settable 实例化 FromStrings,但 Settable 没有 Set 方法。具有 Set 方法的类型是 *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.

这可以编译,但不幸的是它会在运行时出现 panic。 问题在于 FromStrings 创建了 []T 类型的切片。当用 *Settable 实例化时,这意味着 []*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.

FromStrings 调用result[i].Set(v) 时,它会调用存储在 result[i]中的指针上的 Set 方法。该指针为零。 Settable.Set 方法将使用 nil 接收器调用,并且会由于 nil 取消引用错误而引发恐慌。

The pointer type *Settable implements the constraint, but the code really wants to use the non-pointer typeSettable. 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.

指针类型*Settable实现了约束,但是代码要求非指针类型Settable。 我们需要的是一种编写 FromStrings 的方法,它可以将 Settable 类型作为参数但调用指针方法。 重复一遍,我们不能使用 Settable,因为它没有 Set 方法,我们不能使用 *Settable,因为那样我们就不能创建 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:

然后我们可以这样调用 FromStrings2:

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

这种方法按预期工作,但必须在类型参数中重复 Settable 是很尴尬的。 幸运的是,约束类型推断使它不那么尴尬。

使用约束类型推断我们可以写:

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:

无法避免传递类型参数 Settable。 但是给定该类型参数,约束类型推断可以为类型参数 PT 推断类型参数 *Settable。 和以前一样,我们创建一个已知类型参数的映射:

{T -> Settable}

然后我们将每个类型参数与结构约束统一起来。在这种情况下,我们将 PT 统一为 Setter2[T] 的单一类型,即 *T。

现在的映射

{T -> Settable, PT -> *T}

然后我们用Settable代替T,使我们得到:

{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.

在此之后什么都没有改变,我们就完成了。两种type arguments都是已知的。 这个例子展示了我们如何使用约束类型推断来将约束应用于基于其他type parameter的类型。 在这种情况下,我们说 PT,也就是*T,必须有一个 Set 方法。我们可以在不要求调用者显式提及 *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:

即使使用约束类型推断来根据约束推断type arguments,我们仍然必须在确定type arguments后检查约束。 在上面的 FromStrings2 示例中,我们能够根据 Setter2 约束推断出 PTtype arguments。 但在这样做时,我们只查看了类型集,没有查看方法。即使约束类型推断成功,我们仍然必须验证该方法是否存在,是否满足约束。 例如,考虑以下无效代码:

// 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.

进行此调用时,我们将像以前一样应用约束类型推断。 它会像以前一样成功,并推断type arguments[Unsettable, *Unsettable]。 只有在约束类型推断完成后,我们才会检查*Unsettable 是否实现了约束 Setter2[Unsettable]。由于 *Unsettable 没有 Set 方法,约束检查将失败,并且此代码将无法编译。

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:

对于一个泛型函数来说,要求一个type argument和一个参数是类型本身的方法是很有用的。 例如,这在比较方法中很自然地出现。 (请注意,我们在这里谈论的是方法,而不是运算符。) 假设我们要编写一个 Index 方法,它使用 Equal 方法来检查它是否找到了所需的值。

我们想这样写:

// 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.

为了编写 Equaler 约束,我们必须编写一个可以引用传入的type argument的约束。 最简单的方法是利用约束不必是已定义类型这一事实,它可以只是一个接口类型文字。 然后,此接口类型文字可以引用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.

在这个例子中,当我们将 equalInt 传递给 Index 时,我们检查 equalInt 是否实现了约束 interface { Equal(T) bool }。 约束有一个类型参数,因此我们将type parameter替换为type argument,即 equalInt 本身。 这样我们就能得到 interface { Equal(equalInt) bool }equalInt 类型有一个带有该签名的 Equal 方法,编译成功。

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.

在 Go 的当前实现中,接口值始终保存指针。将非指针值放在接口变量中会导致该值被装箱。这意味着实际值存储在其他地方,例如在堆或堆栈上,并且接口值包含指向该位置的指针。

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

在此设计中,泛型类型的值未装箱。 例如,让我们回顾一下我们之前的 FromStrings2 示例。当它以 Settable 类型实例化时,它返回一个 []Settable 类型的值。

// 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.

当我们调用以 Settable 类型实例化的 FromStrings2 时,我们得到一个 []Settable。 该切片的元素将是可设置的值,也就是说,它们将是整数。

它们不会被装箱,即使它们是由泛型函数创建和设置的。

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 }.

实例化时,字段不会被装箱,也不会发生意外的内存分配。类型Pair[int, string] 可转换为 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.

正如前面看到的 Setter2,约束可以同时使用约束元素和方法。

// 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.

String() 字符串的类型集是定义该方法的所有类型的集合。

The type set of StringableSignedInteger is the intersection of those two type sets.

StringableSignedInteger 的类型集是这两个类型集的交集。

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.

结果是所有类型的集合,其基础类型是预先声明的有符号整数类型之一,并且定义了方法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.

使用将 StringableSignedInteger 作为约束的参数化类型 P 的函数可以对类型 P 的值使用任何整数类型(+、* 等)允许的操作。

It may also call the String method on a value of type P to get back a string.

它还可以对类型 P 的值调用 String 方法以取回字符串。

It’s worth noting that the ~ is essential here.

值得注意的是~在这里是必不可少的。

The StringableSignedInteger constraint uses ~int, not int.

StringableSignedInteger 约束使用 ~int,而不是 int

The type int would not itself be permitted as a type argument, sinceint does not have a String method.

类型 int 本身不允许作为类型参数,因为 int 没有 String 方法。

// 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;

通常的规则适用:此约束的类型参数可以是string[]byte

a generic function with this constraint may use any operation permitted by both string and []byte.

具有此约束的通用函数可以使用字符串和 []byte 允许的任何操作。

The byteseq constraint permits writing generic functions that work for either string or []byte types.

byteseq约束允许编写通用函数,对string[]byte类型都有效。

// 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.

对于复合类型(字符串、指针、数组、切片、结构、函数、映射、通道),我们施加了一个额外的限制:只有当运算符接受相同的输入类型(如果有的话)并为所有类型产生相同的结果类型时,才可以使用一个操作类型集中的类型。

需要明确的是,仅当复合类型出现在类型集中时才会施加此附加限制。

当复合类型由类型集之外的类型参数形成时,它不适用,例如在 var v []T 中用于某些类型参数 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.)

(注意: 有可能 随着人们越来越了解Go的泛型,在后续实现上会放宽这个限制)

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.

约束元素中的字面量可以引用约束的type parameters。 在此示例中,通用函数 Map 采用两个type parameters。 第一个type parameters需要有一个基础类型,它是第二个类型参数的一部分。 对第二个type parameters没有限制。

// 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.

在具有两个类型参数 FromTo 的函数中,如果 From 的约束类型集中的所有类型都可以转换为 To 的约束类型集中的所有类型,则 From 类型的值可以转换为 To 类型的值. 这是一般规则的结果,即泛型函数可以使用类型集中列出的所有类型所允许的任何操作。

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.

Convert 中的类型转换是允许的,因为 Go 允许将每个整数类型转换为每个其他整数类型。

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.

我们已经说过,联合元素的类型集是联合中所有类型的类型集的联合。对于大多数类型 TT 的类型集就是 T 本身。然而,对于接口类型(和近似元素),情况并非如此。 不嵌入非接口元素的接口类型的类型集,正如我们前面所说的,是声明接口所有方法的所有类型的集合,包括接口类型本身。在 union 元素中使用这样的接口类型会将该类型集添加到 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.

Stringish的类型集是string类型和所有实现了fmt.Stringer的类型。

任何这些类型(包括 fmt.Stringer 本身)都将被允许作为此约束的类型参数。不允许对使用 Stringish 作为约束的类型参数的值进行任何操作(所有类型都支持的操作除外)。

这是因为fmt.Stringer 属于 Stringish 的类型集,而fmt.Stringer 这种接口类型不支持任何特定于类型的操作。

Stringish 允许的操作是类型集中所有类型都支持的操作,包括 fmt.Stringer,所以在这种情况下,除了所有类型都支持的操作之外,没有其他操作。使用此约束的参数化函数必须使用类型断言或反射才能使用这些值。

不过,这在某些情况下对于更强的静态类型检查可能很有用。要点是它直接遵循类型集和约束满足的定义。

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.

写一个具有空类型集的约束是可能的。 没有一个类型参数可以满足这样的约束,所以任何试图实例化一个使用空类型集的约束的函数都会失败。 一般来说,编译器不可能检测到所有这样的情况。 也许vet tools应该对它能够检测到的情况给出一个错误。

// 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.

这种方法不会尝试处理每个可能的运算符。期望复合类型通常会在泛型函数和类型声明中使用复合类型来处理,而不是将复合类型放在类型集中。例如,我们期望想要索引到切片的函数在切片元素类型 T 上进行参数化,并使用类型为 []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.

如上面的 DoubleMySlice 示例所示,这种方法使得声明接受和返回复合类型并希望返回与其参数类型相同的结果类型的泛型函数变得很尴尬。 定义的复合类型并不常见,但它们确实出现了。这种尴尬是这种方法的弱点。

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.

我们不建议以任何方式更改反射包。当一个类型或函数被实例化时,所有的类型参数都会变成普通的非泛型类型。实例化类型的 reflect.Type 值的 String 方法将返回带有方括号中的类型参数的名称。例如,列表 [int]。 非泛型代码不可能在没有实例化的情况下引用泛型代码,因此没有未实例化的泛型类型或函数的反射信息。

Implementation

Russ Cox famously observed that generics require choosing among slow programmers, slow compilers, or slow execution times.

Russ Cox有句名言:泛型需要在研发人员的工作效率编译器编译的效率程序的执行效率中做出选择。

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.

这种设计是完全向后兼容的。 我们相信这种设计满足了人们对 Go 泛型编程的需求,而不会使语言变得过于复杂。 如果没有多年的这种设计经验,我们就无法真正了解对语言的影响。也就是说,这里有一些猜测。

Author

  • Ian Lance Taylor
  • Robert Griesemer
  • August 20, 2021