【问题标题】:How to define a generic type with a pointer receiver method and a non-pointer receiver method如何使用指针接收器方法和非指针接收器方法定义泛型类型
【发布时间】:2021-11-25 12:50:51
【问题描述】:

我已经开始尝试 Go 泛型。有没有办法定义一个泛型例如:一个方法不需要用指针接收器调用,另一个需要用指针接收器调用。

例子:

type MapKey interface {
    comparable
}

type Unmarshaller[T any] interface {
    UnmarshalJSON(b []byte) error
    *T
}

type Marshaller[T any] interface {
    MarshalJSON() ([]byte, error)
}

type MapValue[T any] interface {
    Marshaller[T]
    Unmarshaller[T]
}

type Map[T any, K MapKey, V MapValue[T]] struct {
    _ K
    _ V
}

func (m Map[K, V]) Save(k K, v V) error {
    _, _ = v.MarshalJSON()
    return nil
}

func (m Map[K, V]) Get(k K) (value V, err error) {
    v := new(V)
    err = v.UnmarshalJSON(m.bytesFromKey(k))
    return *v, nil
}


// here I need to make sure V is actually a pointer so that UnmarshalJSON correctly propagates to the caller of GetTo and no copy is made of V
func (m Map[K, V]) GetTo(k K, v V) error {
    bytesValue := m.kv.Get(m.bytesFromKey(k))
    return v.UnmarshalJSON(v)
}

错误:

./map_generic.go:26:16: V has no constraints
./map_generic.go:27:11: v.MarshalJSON undefined (type bound for V has no method MarshalJSON)
./map_generic.go:31:16: V has no constraints
./map_generic.go:33:10: v.UnmarshalJSON undefined (type *V has no field or method UnmarshalJSON)

【问题讨论】:

  • 尝试解释你这样做的目的,因为我什至不知道那些 functype 声明是否有效。
  • 您在此处发布的代码无法编译,但这是由于与您的问题中的约束无关的几个问题。请提出一个最小的可重现示例
  • 如果你的问题是关于指针而没有指针的方法,这个参考可以帮助你stackoverflow.com/questions/33936081/…
  • 无论如何,我相信你不能可靠地做到这一点。 *T 的方法集包括在T 上声明的方法,因此即使您将类型参数约束为*T,如果您在T*T 上声明UnmarshalJSON,它也将起作用。
  • 那么你可能不需要泛型。不要过度复杂化。只需立即将 V 声明为接口类型,如JSONer,定义为type JSONer interface { UnmarshalJSON([]byte) error; MarshalJSON() ([]byte, error) },并让实现者决定是使用指针还是值接收器。

标签: go generics


【解决方案1】:

我还没有找到一种方法来限制一个接口具有指针方法和非指针方法。 但是我找到了一种方法来拥有一个结构类型,它的方法可以接受 T 或 *T。

我不得不拆分接口(Marshaller、Unmarshaller)并约束它们使用相同的类型,然后Map 类型定义同时使用两者。

这是代码(GOVERSION="devel go1.18-c239790 Sat Nov 13 03:33:55 2021 +0000")

package storage

// Marshaller has T type parameter
type Marshaller[T interface{}] interface {
    MarshalJSON() ([]byte, error)
}

// Unmarshaller has T type parameter and we force it to be a pointer
type Unmarshaller[T interface{}] interface {
    UnmarshalJSON(b []byte) error
    *T
}

// Map so here we need T, and we force Marshaller and Unmarshaller to be using the same type T
type Map[T any, K comparable, MV Marshaller[T], UV Unmarshaller[T]] struct {

}

func NewMap[T any, K comparable, MV Marshaller[T], UV Unmarshaller[T]]() Map[T, K, MV, UV] {
    return Map[T, K, MV, UV]{}
}

// Save accepts the non pointer version of T to use for MarshalJSON
func (m Map[T, K, MV, UV]) Save(k K, v MV) {
    v.MarshalJSON()
}

// GetTo accepts the pointer version of T to use for UnmarshalJSON
func (m Map[T, K, MV, UV]) GetTo(k K, v UV) {
    v.UnmarshalJSON(nil)
}

这是一个测试文件,它断言我们可以强制某些方法接受或不接受相同类型的指针。

package storage

import (
    "testing"
)

type x struct {

}

func (x *x) UnmarshalJSON(b []byte) error {
    return nil
}

func (x x) MarshalJSON() ([]byte, error) {
    return nil, nil
}

// This will compile
func TestMarshalNoPointer(t *testing.T) {
    m := NewMap[x, string, x, *x]()
    m.Save("hi", x{})
}

// This will compile
func TestUnmarshalPointer(t *testing.T) {
    m := NewMap[x, string, x, *x]()
    m.GetTo("hi", &x{})
}

// This won't compile
func TestUnmarshalNoPointer(t *testing.T) {
    m := NewMap[x, string, x, *x]()
    m.GetTo("hi", x{})
}

【讨论】:

  • 这仍然受到我在问题的 cmets 中指出的相同问题的影响,即它仍然适用于在值接收器上声明的 UnmarshalJSONgotipplay.golang.org/p/EKm3gSnur91
  • 是的,这是一场悲剧。但是,至少去兽医会发出这些情况的信号。但我实现了我想要实现的目标 - 我的 Map 类型具有接受 *T 或 T 的方法。
  • 顺便说一句,您可以使用T any 而不是T interface{}
猜你喜欢
  • 1970-01-01
  • 2017-10-08
  • 1970-01-01
  • 2014-07-30
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
相关资源
最近更新 更多