有没有一种方法可以约束(通用)类型参数?

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

Is there a way to constraint (generic) type parameters?

问题

我刚开始学习泛型。所以我正在尝试为我的自定义数据库编写一个通用的驱动程序,用于处理一些 protobuf 消息。

我想找到一种方法来进一步约束我的泛型类型,但是作为一个指针,也就是说,确保(告诉编译器)约束 E 实现了另一个方法。

首先,我约束了 db 可以处理的实体。

type Entity interface {
   pb.MsgA | pb.MsgB | pb.MsgC
}

然后,编写了一个通用接口来描述 db 的功能,以便可以由处理相应 proto 消息的不同服务使用:

type DB[E Entity] interface {
   Get(...) (E, error)
   List(...) ([]E, error)
   ...
}

到目前为止还不错。然而,我还希望这些实体在发送到网络时能够进行(反)序列化,在与数据库通信时能够进行克隆和合并。就像这样:

func Encode[E Entity](v *E) ([]byte, error) {
   return proto.Marshal(v)
}

然而,上面的代码给我报了以下错误:

cannot use val (variable of type *E) as type protoreflect.ProtoMessage in argument to proto.Marshal: *E does not implement protoreflect.ProtoMessage (type *E is pointer to type parameter, not type parameter)

问题是 proto.Marshal 要求实体(*E)实现 proto.Message 接口,即 ProtoReflect() 方法,而我所有的 Entity 类型确实都实现了它,但它没有被约束,编译器无法推断。

我还尝试将实体定义为:

type Entity interface {
   *pb.MsgA | *pb.MsgB | *pb.MsgC
   proto.Message
}

然而,这种方式不太对,而且我需要进行一些额外的 protreflect 操作来实例化我的 proto.Messages,而实体指针引用了它。

英文:

I've just started to learn about generics. So I'm trying to generalize a driver for my custom database operating on some protobuf messages.

I want to find a way to further constraint my generic type but as a pointer, i.e. make sure (tell the compiler) that constraint E implements another method.

First, I've constrained what entities db can handle.

type Entity interface {
   pb.MsgA | pb.MsgB | pb.MsgC
}

Then, wrote a generic interface describing db capabilities so it can be used by different services handling respective proto messages:

type DB[E Entity] interface {
   Get(...) (E, error)
   List(...) ([]E, error)
   ...
}

So far so good. However, I also want these entities to be (de)serialized to be sent on wire, cloned and merged when communicating with the database. Something like this:

func Encode[E Entity](v *E) ([]byte, error) {
   return proto.Marshal(v)
}

However, the code above gives me the following error:

cannot use val (variable of type *E) as type protoreflect.ProtoMessage in argument to proto.Marshal: *E does not implement protoreflect.ProtoMessage (type *E is pointer to type parameter, not type parameter)

The problem is proto.Marshal requires entity (*E) to implement proto.Message interface, namely ProtoReflect() method, which all my Entity types do implement, but it is not constrained and compiler cannot infer.

I have also tried to define entity as:

type Entity interface {
   *pb.MsgA | *pb.MsgB | *pb.MsgC
   proto.Message
}

However, that doesn't feel right in addition to I need to do some extra protreflect operations to instantiate my proto.Messages which Entity pointer reference to.

答案1

得分: 3

你可以这样做:

func Encode[M interface { *E; proto.Message }, E Entity](v M) ([]byte, error) {
	return proto.Marshal(v)
}

如果你想要比上面更简洁的语法,你可以声明消息约束:

type Message[E Entity] interface {
	*E
	proto.Message
}

func Encode[M Message[E], E Entity](v M) ([]byte, error) {
	return proto.Marshal(v)
}

https://go.dev/play/p/AYmSKYCfZ1_l

英文:

You can do something like this:

func Encode[M interface { *E; proto.Message }, E Entity](v M) ([]byte, error) {
	return proto.Marshal(v)
}

If you'd like a more cleaner syntax than the above, you can delcare the message constraint:

type Message[E Entity] interface {
	*E
	proto.Message
}

func Encode[M Message[E], E Entity](v M) ([]byte, error) {
	return proto.Marshal(v)
}

https://go.dev/play/p/AYmSKYCfZ1_l

huangapple
  • 本文由 发表于 2023年1月18日 21:49:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/75160254.html
匿名

发表评论

匿名网友

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

确定