From f4700a32e9da12027aa21e2b149577057fd453a9 Mon Sep 17 00:00:00 2001 From: xx <784457618@qq.com> Date: Thu, 10 Aug 2023 00:39:26 +0800 Subject: [PATCH] =?UTF-8?q?[add]=20=E5=A2=9E=E5=8A=A0=E4=B8=AD=E6=96=87?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E6=8E=89=E8=8B=B1=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2023/w31_all_your_comparable_types.md | 121 ++++++++++++++------------ 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/2023/w31_all_your_comparable_types.md b/2023/w31_all_your_comparable_types.md index e4f764b..91273b5 100644 --- a/2023/w31_all_your_comparable_types.md +++ b/2023/w31_all_your_comparable_types.md @@ -1,4 +1,5 @@ # All your comparable types +# 所有类型都可以比较 - [原文链接](https://go.dev/blog/comparable) - 原文作者:Robert Griesemer @@ -6,47 +7,47 @@ - 译者:[小超人](https://github.com/fcorpo) - 校对:[-](https://github.com/) -On February 1 we released our latest Go version, 1.20, which included a few language changes. Here we'll discuss one of those changes: the predeclared `comparable` type constraint is now satisfied by all [comparable types](https://go.dev/ref/spec#Comparison_operators). Surprisingly, before Go 1.20, some comparable types did not satisfy `comparable`! +2 月 1 号,我们发布了最新的 Go 1.20 版本,其中包括一些语言层面上的变更。在这里,我们将讨论其中一个变更:现在所有可比较的类型都可以满足之前的[可比较类型](https://go.dev/ref/spec#Comparison_operators)约束。令人惊讶的是,在 Go 1.20 之前,一些可比较的类型并不满足可比较性! -If you're confused, you've come to the right place. Consider the valid map declaration +如果你对此感到困惑,那么你来对地方了。那么我们先声明一个有效的 map。 ``` go var lookupTable map[any]string ``` -where the map's key type is `any` (which is a [comparable type](https://go.dev/ref/spec#Comparison_operators)). This works perfectly fine in Go. On the other hand, before Go 1.20, the seemingly equivalent generic map type +其中 map 的键类型为 any(这是一个[可比较的类型](https://go.dev/ref/spec#Comparison_operators))。这在 Go 中可以完美运行。另外,在 Go 1.20 之前,这种定义可以看似等效的泛型的 map 类型 ``` go type genericLookupTable[K comparable, V any] map[K]V ``` -could be used just like a regular map type, but produced a compile-time error when `any` was used as the key type: +是否看似可以像普通的 map 类型一样使用,但是在将 any 用作键类型时产生编译时错误 ``` go var lookupTable genericLookupTable[any, string] // ERROR: any does not implement comparable (Go 1.18 and Go 1.19) ``` -Starting with Go 1.20 this code will compile just fine. +从 Go 1.20 开始,这段代码可以很好地编译。 -The pre-Go 1.20 behavior of `comparable` was particularly annoying because it prevented us from writing the kind of generic libraries we were hoping to write with generics in the first place. The proposed [`maps.Clone`](https://go.dev/issue/57436) function +在 Go 1.20 之前,comparable 的行为特别令人讨厌,因为它阻止了我们编写一开始希望使用泛型编写的泛型库。从 [`maps.Clone`](https://go.dev/issue/57436) 函数的提议 -``` +``` go func Clone[M ~map[K]V, K comparable, V any](m M) M { … } ``` -can be written but could not be used for a map such as `lookupTable` for the same reason our `genericLookupTable` could not be used with `any` as key type. +可以编写,但不能用于像 `lookupTable` 这样的 map,原因与我们的 `genericLookupTable` 不能与 `any` 作为键类型一样。 -In this blog post, we hope to shine some light on the language mechanics behind all this. In order to do so, we start with a bit of background information. +在这篇博文中,我们希望对这一切背后的语言机制有所启发。为了做到这一点,我们从一些背景信息开始。 -## Type parameters and constraints +## 类型参数和约束 -Go 1.18 introduced generics and, with that, [_type parameters_](https://go.dev/ref/spec#Type_parameter_declarations) as a new language construct. +从 Go 1.18 开始引入了泛型,并将[类型参数](https://go.dev/ref/spec#Type_parameter_declarations)作为新的语言结构。 -In an ordinary function, a parameter ranges over a set of values that is restricted by its type. Analogously, in a generic function (or type), a type parameter ranges over a set of types that is restricted by its [_type constraint_](https://go.dev/ref/spec#Type_constraints). Thus, a type constraint defines the _set of types_ that are permissible as type arguments. +在普通函数中,参数的取值范围是受其类型限制的一组值。类似地,在泛型函数(或类型)中,类型参数的范围超过受其类型约束限制的一组类型。因此,类型约束定义了允许作为类型参数的类型集。 -Go 1.18 also changed how we view interfaces: while in the past an interface defined a set of methods, now an interface defines a set of types. This new view is completely backward compatible: for any given set of methods defined by an interface, we can imagine the (infinite) set of all types that implement those methods. For instance, given an [`io.Writer`](https://go.dev/pkg/io#Writer) interface, we can imagine the infinite set of all types that have a `Write` method with the appropriate signature. All of these types _implement_ the interface because they all have the required `Write` method. +GO 1.18 还改变了我们定义接口的方式:在过去一个接口定义了一组方法,现在接口定义了一组类型。这种新方式完全向后兼容:对于由接口定义的任何给定的方法集,我们可以想象所有实现这些方法的(无限)集。例如,给定一个 [`io.Writer`](https://go.dev/pkg/io#Writer) 接口,我们可以想象所有具有适当签名的 `Write` 方法的无限集。所有这些类型都实现了接口,因为它们都具有所需的 `Write` 方法。 -But the new type set view is more powerful than the old method set one: we can describe a set of types explicitly, not only indirectly through methods. This gives us new ways to control a type set. Starting with Go 1.18, an interface may embed not just other interfaces, but any type, a union of types, or an infinite set of types that share the same [underlying type](https://go.dev/ref/spec#Underlying_types). These types are then included in the [type set computation](https://go.dev/ref/spec#General_interfaces): the union notation `A|B` means “type `A` or type `B`”, and the `~T` notation stands for “all types that have the underlying type `T`”. For instance, the interface +但是,从新的类型集合来看比旧方法集更强大:我们可以明确地描述一组类型,而不仅仅是通过方法间接描述。这为我们提供了控制类型集的新方法。从 GO 1.18 开始,接口不仅可以嵌入其他接口,还可以嵌入任何类型,类型的结合或共享相同[基础类型](https://go.dev/ref/spec#Underlying_types)的无限类型。然后将这些类型包含在[类型集计算](https://go.dev/ref/spec#General_interfaces)中:联合符号 `A|B` 表示 "A型或类型B",并且 `〜T` 表示法代表“具有基础类型 `T` 的所有类型”。 ``` go interface { @@ -55,59 +56,59 @@ interface { } ``` -defines the set of all types whose underlying types are either `int` or `string` and that also implement `io.Writer`'s `Write` method. +这个是定义所有基础类型是 `int` 或字符串,并且还实现了 `io.Writer` 的 `Write` 方法的接口。 -Such generalized interfaces can't be used as variable types. But because they describe type sets they are used as type constraints, which are sets of types. For instance, we can write a generic `min` function +这种广义接口不能用作变量类型。但是因为它们描述了类型集,所以它们被用作类型约束,即类型集。例如,我们可以写一个泛型的 `min` 函数 ``` go func min[P interface{ ~int64 | ~float64 }](x, y P) P ``` -which accepts any `int64` or `float64` argument. (Of course, a more realistic implementation would use a constraint that enumerates all basic types with an `<` operator.) +它接受任何 `int64` 或 `float64` 参数。(当然,更好的实现应该使用约束,用 `<` 操作符枚举所有基本类型。) -As an aside, because enumerating explicit types without methods is common, a little bit of [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) allows us to [omit the enclosing `interface{}`](https://go.dev/ref/spec#General_interfaces), leading to the compact and more idiomatic +顺便说一句,由于没有方法的枚举明确类型是常见的,因此一点点的[语法糖](https://en.wikipedia.org/wiki/Syntactic_sugar)允许我们[忽略闭包接口](https://go.dev/ref/spec#General_interfaces),从而更加符合我们的使用习惯。 ``` go func min[P ~int64 | ~float64](x, y P) P { … } ``` -With the new type set view we also need a new way to explain what it means to [_implement_](https://go.dev/ref/spec#Implementing_an_interface) an interface. We say that a (non-interface) type `T` implements an interface `I` if `T` is an element of the interface's type set. If `T` is an interface itself, it describes a type set. Every single type in that set must also be in the type set of `I`, otherwise `T` would contain types that do not implement `I`. Thus, if `T` is an interface, it implements interface `I` if the type set of `T` is a subset of the type set of `I`. +从新的类型集合来看,我们还需要一种新的方式来解释实现[接口](https://go.dev/ref/spec#Implementing_an_interface)意味着什么。如果一个类型 `T` 是接口类型集合的一个元素,那么 (非接口)类型 `T` 实现接口 `I`。如果 `T` 本身是一个接口,它描述了一个类型集。该集合中的每一个类型必须也在 `I` 的类型集合中,否则 `T` 将包含不实现 `I` 的类型。因此,如果 `T` 是一个接口,如果 `T` 的类型集合是`I` 的类型集合的子集,它实现接口 `I`。 -Now we have all the ingredients in place to understand constraint satisfaction. As we have seen earlier, a type constraint describes the set of acceptable argument types for a type parameter. A type argument satisfies the corresponding type parameter constraint if the type argument is in the set described by the constraint interface. This is another way of saying that the type argument implements the constraint. In Go 1.18 and Go 1.19, constraint satisfaction meant constraint implementation. As we'll see in a bit, in Go 1.20 constraint satisfaction is not quite constraint implementation anymore. +现在,我们已经具备了理解满足约束的所有要素。正如我们之前看到的,类型约束描述了可接受参数类型的类型参数集。如果类型参数在约束接口描述的集合中,则类型参数满足相应的类型参数约束。这是说类型参数实现约束的另一种方式。在 GO 1.18 和 GO 1.19 中,满足约束条件意味着实现约束。正如我们将一点点看到的那样,在 GO 1.20 满足约束条件不再是实现约束。 -## Operations on type parameter values +## 对类型参数值的操作 -A type constraint does not just specify what type arguments are acceptable for a type parameter, it also determines the operations that are possible on values of a type parameter. As we would expect, if a constraint defines a method such as `Write`, the `Write` method can be called on a value of the respective type parameter. More generally, an operation such as `+` or `*` that is supported by all types in the type set defined by a constraint is permitted with values of the corresponding type parameter. +类型约束不仅指定类型参数可以接受的类型参数,还确定了类型参数值可能的操作。正如我们所期望的那样,如果一个约束定义了诸如 `Write`,则可以根据相应类型参数的值调用 `Write` 方法。一般来说,由约束定义的类型集中所有类型支持的操作允许使用相应类型参数的值, 比如 `+` 或者 `*` 操作。 -For instance, given the `min` example, in the function body any operation that is supported by `int64` and `float64` types is permitted on values of the type parameter `P`. That includes all the basic arithmetic operations, but also comparisons such as `<`. But it does not include bitwise operations such as `&` or `|` because those operations are not defined on `float64` values. +例如,拿 `min` 函数来举例,在函数体中,允许在包含所有基本算术操作的类型参数 P 的值上使用 int64 和 float64 类型支持的任何操作,但也可以比较 <。但它不包括诸如 `&` 或 `|` 之类的位操作因为这些操作未在 float64 值上定义。 -## Comparable types +## 可比较的类型 -In contrast to other unary and binary operations, `==` is defined on not just a limited set of [predeclared types](https://go.dev/ref/spec#Types), but on an infinite variety of types, including arrays, structs, and interfaces. It is impossible to enumerate all these types in a constraint. We need a different mechanism to express that a type parameter must support `==` (and `!=`, of course) if we care about more than predeclared types. +与其他一般和二进制操作相反,`==` 不仅在有限的[预先声明类型](https://go.dev/ref/spec#Types)中定义,而且在无限的类型上,包括数组,结构和接口。不可能在约束中列举所有这些提到过的类型。如果我们关心的是预期类型,我们需要一种不同的机制来表达类型参数必须支持 `==`(当然了也包括 `!=` )。 -We solve this problem through the predeclared type [`comparable`](https://go.dev/ref/spec#Predeclared_identifiers), introduced with Go 1.18. `comparable` is an interface type whose type set is the infinite set of comparable types, and that may be used as a constraint whenever we require a type argument to support `==`. +我们通过 Go 1.18 中引入的预声明类型 [`comparable`](https://go.dev/ref/spec#Predeclared_identifiers) 解决了这个问题。`Comparable` 是一个类型集是可比较类型的无限集的接口类型,当需要类型实参来支持 `==` 时,可将其用作约束。 -Yet, the set of types comprised by `comparable` is not the same as the set of all [comparable types](https://go.dev/ref/spec#Comparison_operators) defined by the Go spec. [By construction](https://go.dev/ref/spec#Interface_types), a type set specified by an interface (including `comparable`) does not contain the interface itself (or any other interface). Thus, an interface such as `any` is not included in `comparable`, even though all interfaces support `==`. What gives? +但是,可比较组成的类型集与 GO 规格定义的所有[可比类型](https://go.dev/ref/spec#Comparison_operators)的集合并不相同。通过[构造](https://go.dev/ref/spec#Interface_types),接口指定的类型(包括可比较类型)不包含接口本身(或任何其他接口)。因此,即使所有接口支持 ==,诸如 `any` 接口之类的接口也不包含在 `comparable` 中。这是为什么呢? -Comparison of interfaces (and of composite types containing them) may panic at run time: this happens when the dynamic type, the type of the actual value stored in the interface variable, is not comparable. Consider our original `lookupTable` example: it accepts arbitrary values as keys. But if we try to enter a value with a key that does not support `==`, say a slice value, we get a run-time panic: +如果真正保存在接口的动态值并不具备可比较行,那么在运行时可能会发生恐慌,即使是可比较类型的接口和包含可比较性的复合类型。就拿一开始的 `lookupTable` 来举例: 它的键可以接受任意类型的值。但是, 如果我们尝试使用不支持 `==` 运算的类型作为键, 比如说, 切片,那么, 我们会得到运行时的恐慌: ``` go lookupTable[[]int{}] = "slice" // PANIC: runtime error: hash of unhashable type []int ``` -By contrast, `comparable` contains only types that the compiler guarantees will not panic with `==`. We call these types _strictly comparable_. +相比之下,`可比较` 仅仅是编译器保证不会因为 `==` 运算而引发恐慌的类型。我们称这些类型为严格可比较的。 -Most of the time this is exactly what we want: it's comforting to know that `==` in a generic function won't panic if the operands are constrained by `comparable`, and it is what we would intuitively expect. +在大多数情况下,这种结果是符合我们预期的并且是令人欣慰的,如果我们操作数满足 `可比较性` 的约束,`==` 在泛型函数中并不会引发恐慌并且得到我们期望的结果。 -Unfortunately, this definition of `comparable` together with the rules for constraint satisfaction prevented us from writing useful generic code, such as the `genericLookupTable` type shown earlier: for `any` to be an acceptable argument type, `any` must satisfy (and therefore implement) `comparable`. But the type set of `any` is larger than (not a subset of) the type set of `comparable` and therefore does not implement `comparable`. +不幸的是,这种可比较的定义和满足这种约束规则放在一起,导致我们无法编写有效的通用代码,比如,前面提到的 `genericLookupTable` 类型, 对于 `any` 来说可以接受任意类型的参数,但是 `any` 必须满足可比较性。但是, any 是一个满足可比较类型的大集合并且包含了没有实现可比较类型的集合类型的集合。 ``` go var lookupTable GenericLookupTable[any, string] // ERROR: any does not implement comparable (Go 1.18 and Go 1.19) ``` -Users recognized the problem early on and filed a multitude of issues and proposals in short order ([#51338](https://go.dev/issue/51338), [#52474](https://go.dev/issue/52474), [#52531](https://go.dev/issue/52531), [#52614](https://go.dev/issue/52614), [#52624](https://go.dev/issue/52624), [#53734](https://go.dev/issue/53734), etc). Clearly this was a problem we needed to address. +用户很早就意识到了这个问题,并在短时间内提出了许多问题和建议([#51338](https://go.dev/issue/51338), [#52474](https://go.dev/issue/52474), [#52531](https://go.dev/issue/52531), [#52614](https://go.dev/issue/52614), [#52624](https://go.dev/issue/52624), [#53734](https://go.dev/issue/53734), etc)。显然,这是我们需要解决的问题。 -The “obvious” solution was simply to include even non-strictly comparable types in the `comparable` type set. But this leads to inconsistencies with the type set model. Consider the following example: +最简单的解决方案是将不具备严格的可比较性的的类型放入可比较类型的集合中。但是,这将会导致在类型集合的模型中产生不一致的行为,比如说下面这个例子: ``` go func f[Q comparable]() { … } @@ -119,30 +120,32 @@ func g[P any]() { } ``` -Function `f` requires a type argument that is strictly comparable. Obviously it is ok to instantiate `f` with `int`: `int` values never panic on `==` and thus `int` implements `comparable` (case 1). On the other hand, instantiating `f` with `P` is not permitted: `P`'s type set is defined by its constraint `any`, and `any` stands for the set of all possible types. This set includes types that are not comparable at all. Hence, `P` doesn't implement `comparable` and thus cannot be used to instantiate `f` (case 2). And finally, using the type `any` (rather than a type parameter constrained by `any`) doesn't work either, because of exactly the same problem (case 3). +函数 `f` 需要严格可比的类型参数。很明显,我们可以用 int 类型的实例去调用函数 `f`, 因为 int 实现了可比较类型,int 类型的值永远不会在 `==` 运算符上产生恐慌行为(这是场景 1)。另外,如果使用 `P` 去实例化函数 `f`, 这将不会被允许的,因为 `P` 类型定于的约束为 `any`, 而 `any` 则代表了所有可能的集合。这个类型集合包含了根本不具备可比较性的类型,因为,`P` 没有实现可比较性所以不能用来实例化函数 `f`(这是场景 2)。最后,使用 `any`(而不是被 `any` 限制的类型参数) 类型也不能工作,这个问题和上面描述的一样(这是场景 3)。 -Yet, we do want to be able to use the type `any` as type argument in this case. The only way out of this dilemma was to change the language somehow. But how? +但是,在这种情况下,我们依然系统能够使用 `any` 来作为类型参数。唯一能够改变当前困境的方式就是以某种方式改变语言。 但是如果改变呢? -## Interface implementation vs constraint satisfaction +## 接口实现 vs 满足约束 -As mentioned earlier, constraint satisfaction is interface implementation: a type argument `T` satisfies a constraint `C` if `T` implements `C`. This makes sense: `T` must be in the type set expected by `C` which is exactly the definition of interface implementation. +如先前所言,满足约束条件是实现接口, 如果类型参数 `T` 满足 `C` 的约束,那么认为 `T` 实现 `C` . 这是有意义的,实现接口的真正定义是 `T` 必须满足类型 `C` 的期望。 -But this is also the problem because it prevents us from using non-strictly comparable types as type arguments for `comparable`. +但是,这也是一个问题,因为这阻止我们使用不严格的可比较类型作为可比较类型的参数。 -So for Go 1.20, after almost a year of publicly discussing numerous alternatives (see the issues mentioned above), we decided to introduce an exception for just this case. To avoid the inconsistency, rather than changing what `comparable` means, we differentiated between _interface implementation_, which is relevant for passing values to variables, and _constraint satisfaction_, which is relevant for passing type arguments to type parameters. Once separated, we could give each of those concepts (slightly) different rules, and that is exactly what we did with proposal [#56548](https://go.dev/issue/56548). +因此,对于 Go 1.20 来说,经过了一年多的公开讨论多个可替代的方案(请参见上述提议),我们决定仅仅针对这个场景破例一次。避免概念不一致,而不是更改可比较的含义,我们区分了接口实现,这个与类型参数传值有关,和满足约束, 这个和类型参数传递的参数类型有关。我们正针对提案[#56548](https://go.dev/issue/56548)将二者分开定义,一旦分开,我们就可以针对每个概念赋予不同的规则。 -The good news is that the exception is quite localized in the [spec](https://go.dev/ref/spec#Satisfying_a_type_constraint). Constraint satisfaction remains almost the same as interface implementation, with a caveat: +好消息是,这个 [规范](https://go.dev/ref/spec#Satisfying_a_type_constraint) 相当地接地气。并且满足约束和实现接口基本一致,但是仍然有需要注意的地方: > A type `T` satisfies a constraint `C` if > > - `T` implements `C`; or > - `C` can be written in the form `interface{ comparable; E }`, where `E` is a basic interface and `T` is [comparable](https://go.dev/ref/spec#Comparison_operators) and implements `E`. -The second bullet point is the exception. Without going too much into the formalism of the spec, what the exception says is the following: a constraint `C` that expects strictly comparable types (and which may also have other requirements such as methods `E`) is satisfied by any type argument `T` that supports `==` (and which also implements the methods in `E`, if any). Or even shorter: a type that supports `==` also satisfies `comparable` (even though it may not implement it). -We can immediately see that this change is backward-compatible: before Go 1.20, constraint satisfaction was the same as interface implementation, and we still have that rule (1st bullet point). All code that relied on that rule continues to work as before. Only if that rule fails do we need to consider the exception. +第二个要点是一个例外。在没有深入理解规范的情况下,就如期望所说的如下: `C` 的约束是期望严格的可比较类型(并且还有可能其他的需求,比如方法 `E`)是支持 `==` 运算的任意类型 `T` (当然了,也可能实现了 `E` 接口下的多个方法)。简而言之,一个支持 `==` 运算的类型同样也满足了可比较性(即使它可能没有实现它)。 + +我们马上就能看到这个变化的向后兼容性, 在 Go 1.20 之前,满足约束和接口实现是一样的,并且我们依然有这个规则(在第一点)。所有的依赖于改规则的代码依然能够像以前一样继续工作。只有当该规则失败时,我们才需要关注异常。 -Let's revisit our previous example: + +让我们回顾一下前面的例子 ``` go func f[Q comparable]() { … } @@ -156,31 +159,35 @@ func g[P any]() { Now, `any` does satisfy (but not implement!) `comparable`. Why? Because Go permits `==` to be used with values of type `any` (which corresponds to the type `T` in the spec rule), and because the constraint `comparable` (which corresponds to the constraint `C` in the rule) can be written as `interface{ comparable; E }` where `E` is simply the empty interface in this example (case 3). +现在,`any` 类型都满足(但是没有实现)可比性。这是为什么呢?因为 Go 允许 `==` 操作运算符被用在 `any` 类型(这对应规则中的 `T` 类型),因为可比较的约束(对应规则中 C 的约束) `E` 类型还可以写为 `interface {comparable; E}`,其中 `E` 表示空接口(这个场景 3). + Interestingly, `P` still does not satisfy `comparable` (case 2). The reason is that `P` is a type parameter constrained by `any` (it _is not_ `any`). The operation `==` is _not_ available with all types in the type set of `P` and thus not available on `P`; it is not a [comparable type](https://go.dev/ref/spec#Comparison_operators). Therefore the exception doesn't apply. But this is ok: we do like to know that `comparable`, the strict comparability requirement, is enforced most of the time. We just need an exception for Go types that support `==`, essentially for historical reasons: we always had the ability to compare non-strictly comparable types. -## Consequences and remedies +有趣的是,`P` 依然没有满足可比较性(场景 2)。原因是 `P` 是一个受 `any` 约束的类型参数(但是它不是 `any`)。 `==` 运算操作并不适用于 `P` 所在的所有类型的集合也包括 `P` 本身;它并不是[可比较类型](https://go.dev/ref/spec#Comparison_operators)。因此,期望并不成立,不过,没有关系: 我们确实想要知道严格的可比性要求,并且在大部分时间强制执行。出于历史的缘故(我们总是有能力去比较非严格的可比较类型),我们只是想要知道 Go 中支持 `==` 操作的的类型。 + +## 后果和补救措施 -We gophers take pride in the fact that language-specific behavior can be explained and reduced to a fairly compact set of rules, spelled out in the language spec. Over the years we have refined these rules, and when possible made them simpler and often more general. We also have been careful to keep the rules orthogonal, always on the lookout for unintended and unfortunate consequences. Disputes are resolved by consulting the spec, not by decree. That is what we have aspired to since the inception of Go. +我们 Gophers 引以为豪的是,我们在语言规范中用一套相当简洁的规则来解释和简化特定语言的行为。多年来,我们不断完善这些规则,并在可能的情况下让它们变得更简单、更通用。我们还注意保持规则的正交性,时刻警惕意外和不幸的后果。解决争议的方法是查阅规范,而不是颁布法令。这是我们从 Go 诞生之初就一直追求的目标。 -_One does not simply add an exception to a carefully crafted type system without consequences!_ +在精心设计的类型系统中,不能简单地添加一个特性而不产生后果! -So where's the catch? There's an obvious (if mild) drawback, and a less obvious (and more severe) one. Obviously, we now have a more complex rule for constraint satisfaction which is arguably less elegant than what we had before. This is unlikely to affect our day-to-day work in any significant way. +那么,问题出在哪里呢?有一个明显的(或者是较小的)缺点,还有一个不那么明显的(更严重的)缺点。很明显,我们现在有了一个更复杂的约束满足规则,可以说没有以前那么优雅了。但这对我们的日常工作影响不大。 -But we do pay a price for the exception: in Go 1.20, generic functions that rely on `comparable` are not statically type-safe anymore. The `==` and `!=` operations may panic if applied to operands of `comparable` type parameters, even though the declaration says that they are strictly comparable. A single non-comparable value may sneak its way through multiple generic functions or types by way of a single non-strictly comparable type argument and cause a panic. In Go 1.20 we can now declare +但是,我们这是要为这个例外付出代价: 在 Go 1.20 中,依赖于可比较类型的参数不再是静态类型安全的。如果将 `==` 和 `!=` 应用于可比较类型参数的操作数,即使在声明说他们是严格的可比较类型的,也有可能会引起恐慌行为。一个不是可比较类型的值会偷偷穿过多个泛型函数或者类型从而引起恐慌行为。在 Go 1.20 中,我们现在可以声明 ``` go var lookupTable genericLookupTable[any, string] ``` -without compile-time error, but we will get a run-time panic if we ever use a non-strictly comparable key type in this case, exactly like we would with the built-in `map` type. We have given up static type safety for a run-time check. +编译时不会出错,但如果我们在这种情况下使用了非严格可比的键类型,就会在运行时出现恐慌,就像使用内置的 map 类型一样。为了运行时检查,我们放弃了静态类型安全。 -There may be situations where this is not good enough, and where we want to enforce strict comparability. The following observation allows us to do exactly that, at least in limited form: type parameters do not benefit from the exception that we added to the constraint satisfaction rule. For instance, in our earlier example, the type parameter `P` in the function `g` is constrained by `any` (which by itself is comparable but not strictly comparable) and so `P` does not satisfy `comparable`. We can use this knowledge to craft a compile-time assertion of sorts for a given type `T`: +可能在某些情况下,这样做还不够好,我们需要强制执行严格的可比性。下面的观点允许我们至少以有限的形式做到这一点:类型参数并不受益于我们添加到约束满足规则中的例外情况。例如,在我们前面的例子中,函数 `g` 中的类型参数 `P` 受 `any` 约束(`any` 本身具有可比性,但不具有严格可比性),因此 `P` 不满足可比性。我们可以利用这些知识,为给定的 `T` 类型编写一个编译时断言: ``` go type T struct { … } ``` -We want to assert that `T` is strictly comparable. It's tempting to write something like: +我们要断言 T 是严格可比的。我们很想写出这样的内容: ``` go // isComparable may be instantiated with any type that supports == @@ -193,7 +200,7 @@ func isComparable[_ comparable]() {} var _ = isComparable[T] // compile-time error if T does not support == ``` -The dummy (blank) variable declaration serves as our “assertion”. But because of the exception in the constraint satisfaction rule, `isComparable[T]` only fails if `T` is not comparable at all; it will succeed if `T` supports `==`. We can work around this problem by using `T` not as a type argument, but as a type constraint: +虚变量(空白)声明就是我们的 "断言"。但由于约束满足规则中的例外情况,`isComparable[T]` 只有在 `T` 完全不可比的情况下才会失败;如果 `T` 支持 `==` 则会成功。 我们可以不把 `T` 作为类型参数,而是作为类型约束来解决这个问题: ``` go func _[P T]() { @@ -201,12 +208,12 @@ func _[P T]() { } ``` -Here is a [passing](https://go.dev/play/p/9i9iEto3TgE) and [failing](https://go.dev/play/p/5d4BeKLevPB) playground example illustrating this mechanism. +下面是一个演示场中有一个[通过](https://go.dev/play/p/9i9iEto3TgE)和[失败](https://go.dev/play/p/5d4BeKLevPB)的示例,说明了这一机制。 -## Final observations +## 最后意见 -Interestingly, until two months before the Go 1.18 release, the compiler implemented constraint satisfaction exactly as we do now in Go 1.20. But because at that time constraint satisfaction meant interface implementation, we did have an implementation that was inconsistent with the language specification. We were alerted to this fact with [issue #50646](https://go.dev/issue/50646). We were extremely close to the release and had to make a decision quickly. In the absence of a convincing solution, it seemed safest to make the implementation consistent with the spec. A year later, and with plenty of time to consider different approaches, it seems that the implementation we had was the implementation we want in the first place. We have come full circle. +有趣的是,直到 Go 1.18 发布前两个月,编译器实现满足约束的方式与我们现在在 Go 1.20 中实现的方式完全相同。但由于当时的满足约束意味着接口实现,我们的实现确实与语言规范不一致。第 [issue #50646](https://go.dev/issue/50646) 号问题提醒了我们这一事实。当时距离发布时间已经非常接近,我们必须尽快做出决定。在没有令人信服的解决方案的情况下,最稳妥的做法似乎是使实现与规范保持一致。一年后,我们有足够的时间来考虑不同的方法,现在看来,我们所采用的实现方式正是我们最初想要的实现方式。我们绕了一个大圈。 -As always, please let us know if anything doesn't work as expected by filing issues at [https://go.dev/issue/new](https://go.dev/issue/new). +如果有任何不尽如人意的地方,请一如既往地通过 [https://go.dev/issue/new](https://go.dev/issue/new) 向我们提出问题。 -Thank you! +谢谢!