英文:
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.Stringer
或struct { 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] // <---- doesn'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;
, where
> E }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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论