Search code examples
gogenericsprotocol-buffers

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


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.


Solution

  • 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