使用泛型:type *T 是指向类型参数的指针,而不是类型参数本身。

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

Go with Generics: type *T is pointer to type parameter, not type parameter

问题

可能是一个Go语言初学者的问题 使用泛型:type *T 是指向类型参数的指针,而不是类型参数本身。

当我尝试编译下面的代码时,我遇到了以下编译器错误。

我想要为不同类型(这里是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 使用泛型:type *T 是指向类型参数的指针,而不是类型参数本身。

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) {}

你可以在以下链接中找到更详细的示例和变体:

关于实现约束条件

这个实例化 &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:

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

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

发表评论

匿名网友

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

确定