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

Time:01-20

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.

CodePudding user response:

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

  • Related