英文:
Go with Generics: type *T is pointer to type parameter, not type parameter
问题
可能是一个Go语言初学者的问题
当我尝试编译下面的代码时,我遇到了以下编译器错误。
我想要为不同类型(这里是A和B)实现一个对象存储,它们共享一个公共的ID字段。根据DRY原则,我想要使用泛型来实现存储。
当添加一个对象时,我想要使用GS接口来设置它的ID字段(实际代码当然更复杂),但是编译器不允许我这样做。
./prog.go:29:7: item.SetId undefined (type *T is pointer to type parameter, not type parameter)
./prog.go:34:24: A does not implement GS (SetId method has pointer receiver)
有没有推荐的解决方法?提前谢谢!!
package main
import "fmt"
type A struct {
ID string
AMember string
}
type B struct {
ID string
BMember string
}
type GS interface {
Id() string
SetId(string)
}
func (s A) Id() string { return s.ID }
func (s *A) SetId(i string) { s.ID = i }
func (s B) Id() string { return s.ID }
func (s *B) SetId(i string) { s.ID = i }
type MyStore[T GS] struct {
values map[string]*T
}
func (s *MyStore[T]) add(item *T) {
item.SetId("aa")
s.values["aa"] = item
}
func main() {
var storeA = &MyStore[A]{}
storeA.values = make(map[string]*A)
a := &A{}
storeA.add(a)
fmt.Println(a.Id())
}
英文:
Probably a golang beginner's question
I'm facing following compiler error when trying to compile the code below.
I want to implement an object store for different types (here A and B) sharing a common ID field. Following the DRY idea, I want to implement the store using generics.
When adding an object, I want to set its ID field using the GS interface (the actual code is a bit more complex of course), but the compiler does not want me to do that.
> ./prog.go:29:7: item.SetId undefined (type *T is pointer to type parameter, not type parameter)
>
> ./prog.go:34:24: A does not implement GS (SetId method has pointer receiver)
Is there a recommended way to solve this? Thanks in advance!!
package main
import "fmt"
type A struct {
ID string
AMember string
}
type B struct {
ID string
BMember string
}
type GS interface {
Id() string
SetId(string)
}
func (s A) Id() string { return s.ID }
func (s *A) SetId(i string) { s.ID = i }
func (s B) Id() string { return s.ID }
func (s *B) SetId(i string) { s.ID = i }
type MyStore[T GS] struct {
values map[string]*T
}
func (s *MyStore[T]) add(item *T) {
item.SetId("aa")
s.values["aa"] = item
}
func main() {
var storeA = &MyStore[A]{}
storeA.values = make(map[string]*A)
a := &A{}
storeA.add(a)
fmt.Println(a.Id())
}
答案1
得分: 20
关于使用 *T
简而言之,类型参数不是其约束条件。约束条件只决定了在 T
上可用的操作,它并不意味着关于 *T
的任何内容,现在它只是一个无名指针类型。这就是以下语句的含义:
type *T is pointer to type parameter, not type parameter
因此,在你的情况下,*T
的方法集不会自动包括在 T
的具体类型 A
上声明的指针接收器方法,并且它也不会实现由 *A
实现的接口。
你需要通过设置额外的约束条件来明确告诉编译器。简化形式如下:
func Foo[T any, PT interface { SetId(string); *T}](v T) {}
你可以在以下链接中找到更详细的示例和变体:
- https://stackoverflow.com/questions/71440697/go-1-18-generics-how-to-define-a-new-able-type-parameter-with-interface
- https://stackoverflow.com/questions/69573113/how-can-i-instantiate-a-new-pointer-of-type-argument-with-generic-go/69575720#69575720
- https://stackoverflow.com/questions/72090387/what-is-the-generic-type-for-a-pointer-that-implements-an-interface/72091526#72091526
关于实现约束条件
这个实例化 &MyStore[A]{}
失败的原因在错误消息中有明确说明:
A does not implement GS (SetId method has pointer receiver)
换句话说,SetId()
是在 *A
上声明的,而不是在 A
上声明的。因此,你应该使用 *A
来实例化 MyStore
:
var storeA = &MyStore[*A]{}
然后,在结构体/方法定义中将出现的 *T
改为 T
:
type MyStore[T GS] struct {
values map[string]T // 使用 T 而不是 *T
}
func (s *MyStore[T]) add(item T) {
}
使用 *A
实例化后,字段的类型将变为 map[string]*A
,因此使得赋值 storeA.values = make(map[string]*A)
有效,并且方法签名变为 add(item *A)
,从而允许 storeA.add(&A{})
。
修复后的 playground:https://gotipplay.golang.org/p/dcUVJ5YQK_b
英文:
About using *T
In short, a type parameter is not its constraint. The constraint only determines what operations are available on T
, it doesn't imply anything about *T
, which is now just an unnamed pointer type. This is the meaning of:
> type *T is pointer to type parameter, not type parameter
As a consequence, as in your case, the method set of *T
does not automatically include pointer receiver methods declared on the T
's concrete type A
, and it does not implement interfaces that would be implemented by *A
.
You'd have to make that explicit to the compiler by setting additional constraints. In a simplified form it would be something like:
func Foo[T any, PT interface { SetId(string); *T}](v T) {}
You can find more extensive examples and variations on this use case here:
- https://stackoverflow.com/questions/71440697/go-1-18-generics-how-to-define-a-new-able-type-parameter-with-interface
- https://stackoverflow.com/questions/69573113/how-can-i-instantiate-a-new-pointer-of-type-argument-with-generic-go/69575720#69575720
- https://stackoverflow.com/questions/72090387/what-is-the-generic-type-for-a-pointer-that-implements-an-interface/72091526#72091526
About implementing constraints
The reason this instantiation &MyStore[A]{}
fails is clearly reported by the error message:
> A does not implement GS (SetId method has pointer receiver)
In other words SetId()
is declared on *A
, and not A
. Therefore you should instantiate MyStore
with *A
:
var storeA = &MyStore[*A]{}
Then change the occurrences of *T
in the struct/method definition to T
:
type MyStore[T GS] struct {
values map[string]T // just T instead of *T
}
func (s *MyStore[T]) add(item T) {
}
Upon instantiation with *A
the type of the field would become equivalent to map[string]*A
thus making the assignment storeA.values = make(map[string]*A)
valid, and the method signature to add(item *A)
thus allowing storeA.add(&A{})
.
Fixed playground: https://gotipplay.golang.org/p/dcUVJ5YQK_b
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论