在Go 1.20中如何在编译时确保严格的可比性?

huangapple go评论72阅读模式
英文:

Ensure strict comparability at compile time in Go 1.20?

问题

在Go 1.18和Go 1.19中,我可以在编译时确保类型是严格可比较的,即它支持==!=运算符,并且这些运算符在运行时不会引发恐慌

这对于避免无意中向结构体添加可能导致意外恐慌的字段非常有用。

我刚刚尝试使用它来实例化comparable

// 支持 == 和 !=,但比较可能在运行时引发恐慌
type Foo struct {
    SomeField any
}

func ensureComparable[T comparable]() {
    // 空操作
}

var _ = ensureComparable[Foo] // 无法编译,因为Foo的比较可能引发恐慌

这在Go 1.18和1.19中是可能的,因为comparable约束的定义非常明确:

> 预声明的接口类型comparable表示所有可比较的非接口类型的集合

尽管Go 1.18和1.19规范未提及那些既不是接口也不是严格可比较的类型,例如[2]fmt.Stringerstruct { foo any },但gc编译器会拒绝将它们作为comparable的参数。

带有多个示例的Playground:https://go.dev/play/p/_Ggfdnn6OzZ

在Go 1.20中,实例化comparable将与更广泛的可比较性概念保持一致。这使得ensureComparable[Foo]编译通过,即使我不希望它通过

是否有办法在Go 1.20中静态地确保严格的可比较性?

英文:

In Go 1.18 and Go 1.19 I can ensure at compile time that a type is strictly comparable, i.e. it supports == and != operators and those are guaranteed to not panic at run time.

This is useful for example to avoid inadvertently adding fields to a struct that could cause unwanted panics.

I just attempt to instantiate comparable with it:

// supports == and != but comparison could panic at run time
type Foo struct {
    SomeField any
}

func ensureComparable[T comparable]() {
    // no-op
}

var _ = ensureComparable[Foo] // doesn't compile because Foo comparison may panic

This is possible in Go 1.18 and 1.19 due to the very definition of the comparable constraint:

> The predeclared interface type comparable denotes the set of all non-interface types that are comparable

Even though the Go 1.18 and 1.19 spec fail to mention types that are not interfaces but also not strictly comparable, e.g. [2]fmt.Stringer or struct { foo any }, the gc compiler does reject these as arguments for comparable.

Playground with several examples: https://go.dev/play/p/_Ggfdnn6OzZ

With Go 1.20, instantiating comparable will be aligned with the broader notion of comparability. This makes ensureComparable[Foo] compile even though I don't want it to.

Is there a way to statically ensure strict comparability with Go 1.20?

答案1

得分: 6

为了测试在Go 1.20中Foo是否是严格可比较的,需要使用一个由Foo约束的类型参数来实例化ensureComparable

// 保持不变
type Foo struct {
    SomeField any
}

// 保持不变
func ensureComparable[T comparable]() {}

// T由Foo约束,使用T实例化ensureComparable
func ensureStrictlyComparable[T Foo]() {
    _ = ensureComparable[T] // <---- 无法编译通过
}

<sub>这个解决方案最初是由Robert Griesemer在这里提出的(https://github.com/golang/go/issues/56548#issuecomment-1317673963)。</sub>

<hr>

那么这是如何工作的呢?

Go 1.20引入了在实现接口和满足约束之间的区别:

> 如果类型T满足约束C,则
>
> - T实现了C;或者
> - C可以写成interface{ comparable; E }的形式,其中E是基本接口,而T是可比较的并且实现了E

第二个要点是允许接口和具有接口的类型实例化comparable的例外情况。

因此,现在在Go 1.20中,类型Foo本身可以由于满足性例外而实例化comparable。但是类型参数T并不是Foo。类型参数的可比性是以不同的方式定义的:

> 如果类型参数是严格可比的(参见下文),则类型参数是可比的。
>
> [...]
>
> 如果类型参数的类型集中的所有类型都是严格可比的,则类型参数是严格可比的。

类型集中包含一个不是严格可比的类型Foo(因为它具有一个接口字段),因此T不满足comparable。即使Foo本身满足。

这个技巧有效地使得程序在运行时如果Foo的运算符==!=可能会引发恐慌时无法编译通过。

英文:

To test that Foo is strictly comparable in Go 1.20, instantiate ensureComparable with a type parameter constrained by Foo.

// unchanged
type Foo struct {
    SomeField any
}

// unchanged
func ensureComparable[T comparable]() {}

// T constrained by Foo, instantiate ensureComparable with T
func ensureStrictlyComparable[T Foo]() {
    _ = ensureComparable[T] // &lt;---- doesn&#39;t compile
}

<sub>This solution has been originally suggested by Robert Griesemer here.</sub>

<hr>

So how does it work?

Go 1.20 introduces a difference between implementing an interface and satisfying a constraint:

> A type T satisfies a constraint C if
>
> - T implements C; or
> - C can be written in the form interface{ comparable;
&gt; E }
, where E is a basic interface and T is comparable and implements
> E.

The second bullet point is the exception that permits interfaces, and types with interfaces, to instantiate comparable.

So now in Go 1.20 the type Foo itself can instantiate comparable due to the satisfiability exception. But the type parameter T isn't Foo. Comparability of type parameters is defined differently:

> Type parameters are comparable if they are strictly comparable (see below).
>
> [...]
>
> Type parameters are strictly comparable if all types in their type set are strictly comparable.

The type set of T includes a type Foo that is not strictly comparable (because it has an interface field), therefore T doesn't satisfy comparable. Even though Foo itself does.

This trick effectively makes the program fail to compile if Foo's operators == and != might panic at run time.

huangapple
  • 本文由 发表于 2022年12月15日 04:32:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/74803876.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定