1

I have come across the oneof field concept while building grpc services definition (C++ server/client). However, in gRPC web page, I couldn't find proper documentation of oneof usage (write and read operation), any lead on this will be really helpful.

Looking at grpc generated code for Services.proto, it's hard to understand which are usable public methods. Although, I could find - set_status() is there for setting StatusCode.status field and but couldn't find similar set_myfoo() or set_mybar() methods.

Service.proto

message Foo {
   required uint32 id = 1;
}

message Bar {
   required uint32 aID = 1;
   required bytes metadata = 3;
   required bytes payload = 4;
}

message ServiceMessageRequest {
    required uint32 status = 1;

    oneof data {
        Foo myfoo = 2;
        Bar mybar = 3;
    }
}

how these oneof fields - ServiceMessageRequest.data.myfoo and ServiceMessageRequest.data.mybar and thier metadata/payload field can be set?

2 Answers2

1

As per protobuf docs:

In your generated code, oneof fields have the same getters and setters as regular fields. You also get a special method for checking which value (if any) in the oneof is set.

Setting a oneof field will automatically clear all other members of the oneof. So if you set several oneof fields, only the last field you set will still have a value.

From https://protobuf.dev/reference/cpp/cpp-generated/

void set_allocated_foo(string* value): Sets the string object to the field and frees the previous field value if it exists. If the string pointer is not NULL, the message takes ownership of the allocated string object. The message is free to delete the allocated string object at any time, so references to the object may be invalidated. Otherwise, if the value is NULL, the behavior is the same as calling clear_foo().

string* release_foo(): Releases the ownership of the field and returns the pointer of the string object. After calling this, caller takes the ownership of the allocated string object and foo() will return the empty string/empty bytes

On generating your given Service.proto file with protoc 3.21.12, I could see the following lines in Service.pb.h:

  // .Foo myfoo = 2;
  bool has_myfoo() const;
  private:
  bool _internal_has_myfoo() const;
  public:
  void clear_myfoo();
  const ::Foo& myfoo() const;
  PROTOBUF_NODISCARD ::Foo* release_myfoo();
  ::Foo* mutable_myfoo();
  void set_allocated_myfoo(::Foo* myfoo);
  private:
  const ::Foo& _internal_myfoo() const;
  ::Foo* _internal_mutable_myfoo();
  public:
  void unsafe_arena_set_allocated_myfoo(
      ::Foo* myfoo);
  ::Foo* unsafe_arena_release_myfoo();

In your case, you should be able to use like this (as per example in the docs link):

Foo* foo = new Foo;
foo->set_id(1);
...
ServiceMessageRequest service_message_request;
service_message_request.set_allocated_myfoo(foo);
...

Links:

  1. Protobuf: Will set_allocated_* delete the allocated object?
  2. Google Protobuf : "mutable_foo()" or "set_allocated_foo()"?
kiner_shah
  • 3,939
  • 7
  • 23
  • 37
  • It may be true for simple data types - string/int etc. but certainly not for structures (Foo/Bar). As mentioned in question, there are no such methods for setting oneof field in generated code. However, the document link seems to be useful. Thanks for sharing. – Prince Rambade Mar 13 '23 at 07:29
  • @PrinceRambade, read the docs, oneof is just a union. You have to understand what is union and how it works. Setting one field of oneof will set all fields of union because they share the same memory. – kiner_shah Mar 13 '23 at 07:32
  • I know how union works, and also the fact that even to set one field, there should be a publicly accessible method or data member. – Prince Rambade Mar 13 '23 at 08:03
  • @PrinceRambade, I don't understand your comment at all. It's not clear what you are trying to achieve. Maybe you should edit your question accordingly. – kiner_shah Mar 13 '23 at 08:05
  • So what you are suggesting is not correct because there are no `set_myfoo()` or `set_mybar()` method in generated code - which is also clearly mention in the question !! – Prince Rambade Mar 13 '23 at 08:05
  • Usually protoc compiler generates these APIs if its present in the proto file. If it's not generating in your case, then no idea what's happening. Maybe raise an issue in their github repo. – kiner_shah Mar 13 '23 at 08:07
1

Short Answer :

  • Oneof fields can be set using either mutable_* or set_allocated_* methods, but there is a catch !!
  1. when using set_allocated_* then memory management should be handled by developer and memory should be dynamically allocated on heap (local variables cause double free and segmentation issues) - check this answer
  2. when using mutable_* then gRPC will take care of memory management. Thus, it's an easier and preferred option. - check this answer

Detailed Explanation :

For better understanding, assume a simple string type str is added in ServiceMessageRequest (from original question) : [Now it has both basic and complex types in oneof data]

message ServiceMessageRequest {
    required uint32 status = 1;

    oneof data {
        string str = 2;
        Foo myfoo = 3;
        Bar mybar = 4;
    }
}

Setting oneof fields can be straight forward (as below), if they are simple types - integer/string etc. protobuff compiler generates the respective setter/getter for it.

ServiceMessageRequest msg;
msg.set_status(1);
msg.set_str("string");   //setting oneof field

However, if oneof fields have sub structures (or some may call "sub-messages"), then they are set slightly differently (as below) :

ServiceMessageRequest msg;
msg.set_status(10);
msg.set_str("oneof string");   //setting oneof field

// there is no need to release myfoo, it will be released by gRPC.    
Foo* myfoo = msg.mutable_myfoo();   //set oneof as myfoo
myfoo->set_id(20);                   //set the sub-message params

Do remember :

if several oneof fields are set in sequence, only the last oneof will be consider as final set value.

Reference :