在Go泛型中,如何为联合约束中的类型使用通用方法?

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

In Go generics, how to use a common method for types in a union constraint?

问题

我正在尝试理解Go泛型(v1.18)中类型联合约束的用法。以下是我尝试的代码:

type A struct {
}

type B struct {
}

type AB interface {
	*A | *B
}

func (a *A) some() bool {
	return true
}

func (b *B) some() bool {
	return false
}

func some[T AB](x T) bool {
	return x.some()   // <- error
}

编译器报错:

x.some 未定义(类型 T 没有名为 some 的字段或方法)

为什么会这样?如果我不能使用类型 *A*B 的共享方法,那么定义 *A | *B 类型联合有什么意义呢?

(显然,我可以定义一个具有共享方法的接口,并直接使用它。但在我的特定用例中,我想明确限制为特定的类型。)

英文:

I'm trying to understand the usage of the type union constraint in Go generics (v1.18). Here is the code I tried:

type A struct {
}

type B struct {
}

type AB interface {
	*A | *B
}

func (a *A) some() bool {
	return true
}

func (b *B) some() bool {
	return false
}

func some[T AB](x T) bool {
	return x.some()   // &lt;- error
}

The compiler complains:

> x.some undefined (type T has no field or method some)

Why is that? If I cannot use a shared method of type *A and *B, what's the point of defining types union *A | *B at all?

(Apparently I can define an interface with the shared method and directly use that. But in my particular use case I want to restrict to the certain types explicitly.)

答案1

得分: 11

将方法添加到接口约束中,而不放弃泛型:

type AB interface {
	*A | *B
	some() bool
}

func some[T AB](x T) bool {
    return x.some()   // works
}

这将限制T*A*B类型,并声明some() bool方法。

然而,正如你已经发现的,这只是一个变通方法。你是对的,它应该只使用类型联合就能工作。这是Go 1.18的一个限制。令人困惑的是,语言规范似乎仍然支持你的理论(方法集):

接口类型的方法集是接口类型集合中每个类型的方法集的交集(通常结果方法集只是接口中声明的方法集)。

这个限制似乎只在Go 1.18发布说明中有记录:

当前的泛型实现有以下限制:

[...]
如果方法m是由类型参数类型P的约束接口显式声明的,那么Go编译器目前只支持在类型参数类型P的值x上调用方法m。即使m可能通过P的方法集来实现。我们希望在Go 1.19中解除这个限制。

Go跟踪器中的相关问题是#51183,其中包括Griesemer的确认和决定保留语言规范,并记录这个限制。

英文:

Add the method to the interface constraint, without forgoing generics:

type AB interface {
	*A | *B
	some() bool
}

func some[T AB](x T) bool {
    return x.some()   // works
}

This restricts T to types that are either *A or *B and declare some() bool method.

However, as you already found out, this is a workaround. You are right that it should work with the type union alone. It's a limitation of Go 1.18. The confusing part is that the language specifications still seem to support your theory (Method sets):

> The method set of an interface type is the intersection of the method sets of each type in the interface's type set (the resulting method set is usually just the set of declared methods in the interface).

This limitation appears to be documented only in the Go 1.18 release notes:

> The current generics implementation has the following limitations:
>
> [...]
> The Go compiler currently only supports calling a method m on a value x of type parameter type P if m is explicitly declared by P's constraint interface. [...] even though m might be in the method set of P by virtue of the fact that all types in P implement m. We hope to remove this restriction in Go 1.19.

The relevant issue in the Go tracker is #51183, with Griesemer's confirmation and the decision to leave the language specifications as is, and document the restriction.

答案2

得分: 4

AB的声明更改为:

type AB interface {
	*A | *B
	some() bool
} 

在泛型Go中,约束是接口。如果类型参数实现了其约束,则该类型参数是有效的。

请观看有关泛型的Gophercon视频以更好地理解:

为了确保我理解了你的问题,请在Go Playground的“Go Dev分支”模式中运行下面的代码片段:

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type A struct {
}

type B struct {
}

type C struct{}

type AB interface {
	*A | *B
	some() bool
}

func (a *A) some() bool {
	return true
}

func (b *B) some() bool {
	return false
}

func (c *C) some() bool {
	return false
}

func some[T AB](x T) bool {
	return x.some()
}

func main() {
	p := new(A)
	fmt.Println(some(p))

    //uncomment the lines below to see that type C is not valid
	//q := new(C)
	//fmt.Println(some(q))

}

请在Go Playground中运行代码以查看结果。

英文:

Change the declaration of AB to

type AB interface {
	*A | *B
	some() bool
} 

In Generic Go, constraints are interfaces. A type argument is valid if it implements its constraints.

Please watch the Gophercon videos on Generics for a better understanding:

To ensure that I understood your question please run the code snippet below in Go Playground in “Go Dev branch” mode:

// You can edit this code!
// Click here and start typing.
package main

import &quot;fmt&quot;

type A struct {
}

type B struct {
}

type C struct{}

type AB interface {
	*A | *B
	some() bool
}

func (a *A) some() bool {
	return true
}

func (b *B) some() bool {
	return false
}

func (c *C) some() bool {
	return false
}

func some[T AB](x T) bool {
	return x.some()
}

func main() {
	p := new(A)
	fmt.Println(some(p))

    //uncomment the lines below to see that type C is not valid
	//q := new(C)
	//fmt.Println(some(q))

}

答案3

得分: 1

我认为旧的interface{}已经足够完成这个任务。

像这样:

type AB interface {
    some() bool
}

但是如果你想使用泛型,你必须先改变类型。

像这样:

func some[T AB](x T) bool {
	if a, ok := interface{}(x).(*A); ok {
		return a.some()
	}

	return (*B)(x).some()
}
英文:

I think the old interface{} is enough to do this.

Like this:

type AB interface {
    some() bool
}

But if you want to use generic, you must change the type first.

Like this:

func some[T AB](x T) bool {
	if a, ok := interface{}(x).(*A); ok {
		return a.some()
	}

	return (*B)(x).some()
}

huangapple
  • 本文由 发表于 2022年3月7日 13:05:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/71376627.html
匿名

发表评论

匿名网友

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

确定