83

The proto2 version of Protocol Buffers allows to specify default values for message elements:

optional double scaling_factor = 3 [default = 1.0];

Why is this no longer possible in proto3? I consider this a neat feature to save additional bytes on the wire without the need of writing any wrapper code.

Daniel Pauli
  • 963
  • 1
  • 7
  • 8

3 Answers3

109

My understanding is that proto3 no longer allows you to detect field presence and no longer supports non-zero default values because this makes it easier to implement protobufs in terms of "plain old structs" in various languages, without the need to generate accessor methods. This is perceived as making Protobuf easier to use in those languages.

(I personally think that languages which lack accessors and properties aren't very good languages and protobuf should not design down to them, but it's not my project anymore.)

Kenton Varda
  • 41,353
  • 8
  • 121
  • 105
  • 3
    Seems a shame they can't simply detect field presence on the wire and apply the contractual default when missing. That in itself should be language agnostic. – Meirion Hughes Jan 05 '16 at 14:08
  • 7
    @MeirionHughes - I believe the problem comes when instantiating a new object, not off the wire. If your language provides no concept of constructors and no concept of accessors then you can't initialize the fields to default values, independent of serialization. – Kenton Varda Jan 05 '16 at 18:21
  • 1
    But surely you can simply assign the struct fields after construction? I can't think of any sane reason why you'd have a language that defines uninitialisable, immutable structs... – Meirion Hughes Jan 05 '16 at 18:54
  • 2
    @MeirionHughes Sure but the whole point of a default value is that it should be set automatically. If you don't have constructors, you have no way to automatically initialize an object when it is allocated in application code; the app code author would need to always call some initialization function manually. In all likelihood, people will forget. – Kenton Varda Jan 05 '16 at 21:22
  • 1
    Seems odd to break backward compatibility to save you from the use of an object factory in some languages... even so, the "default" directive is a contract feature; in that it only relates to the wire transport. If the POCO object fields differ to the contract-default, then those fields simply get sent out. Seeing as you always use a library for deserialisation, missing fields can easily be initialised to the contract-default by the library itself. Anyway, I'll stop preaching to the guy that knows this more than I. :P – Meirion Hughes Jan 06 '16 at 08:37
  • @MeirionHughes The main purpose of default values was compatibility: old code which doesn't set the field would get a reasonable default. It's important that simply recompiling the code with the new .proto definition (without updating it to use the new fields) doesn't change the behavior of the program, therefore it's important that new fields are in fact initialized to their defaults. It's actually not primarily about serialization; in proto2 a field explicitly set to its default would still be encoded, you had to "clear" it to avoid that. Anyway, I definitely disagree with removing them. – Kenton Varda Jan 07 '16 at 17:56
  • @KentonVarda I was under the impression that default values were a way of optimising message size IE if you build an object that matches the default value then there's no need to send that field over as the protocol defines the value on either end. If you can no longer provide default values all these values that never needed to be serialised now have to be serialised, increasing message size for the purposes of specific languages, which most people may never use. Seems like an odd design decision but I might be missing something... – alex.p Feb 23 '18 at 09:28
  • @KentonVarda oops sorry I just realised you were addressing the same point I made in my last comment, ignore. – alex.p Feb 23 '18 at 09:30
  • @KentonVarda does CapnProto support defaults? – Janac Meena Aug 28 '19 at 17:46
  • @JanacMeena Yes – Kenton Varda Aug 29 '19 at 15:00
  • Seems its enabled since `3.12` as an experimental! feature. [link](https://github.com/protocolbuffers/protobuf/releases/tag/v3.12.0) – Hossein Aug 12 '20 at 04:40
  • @KentonVarda I would have expected them to instead apply go's solution to not having constructors -- ctor functions. In a language that doesn't have constructors, people don't expect automatic construction, and they write explicit constructor functions (c: my_init(), go: NewMysql). So if they were "coding down" to go or c, they missed by a CS degree. – kfsone Aug 31 '20 at 22:29
  • Sorry to interrupt, but what a lovely statement: "but it's not my project anymore.)" good job @KentonVarda – Basheer AL-MOMANI Feb 12 '23 at 18:08
7

This is a work around instead of a direct answer to your question, but I've found myself using wrappers.proto optional values and then setting the default value myself programatically when I absolutely must know if this was a default value or a value that was explicitly set.

Not optimal that your code has to enforce the value instead of the generated code itself, but if you own both sides, at least it's a viable alternative versus having no idea if the value was the default or explicity set as such, especially when looking at a bool set to false.

I am unclear how this affects bytes on the wire. For the instances where I've used it, message length was not a design constraint.

Proto File

import "google/protobuf/wrappers.proto";

google.protobuf.BoolValue optional_bool = 1;

Java code

//load or receive message here
if( !message.hasOptionalBool() )
    message.setOptionalBool( BoolValue.newBuilder().setValue( true ) );
Evan
  • 2,441
  • 23
  • 36
  • Working Thank you!!!! Adding to your answer to convert Bool to *wrapper.Bool. Do these steps 1. import "github.com/golang/protobuf/ptypes/wrappers" 2. To convert &wrappers.BoolValue{Value:v.PkgIsEnabled } – infiniteLearner Jul 16 '21 at 06:34
4

In my autogenerated file .pb.cc I see few places like this:

if (this->myint() != 0) {

and few like this:

myint_ = 0;

So, why not to enable default value and generate

static ::google::protobuf::int32 myint_defaultvalue = 5;

...
if (this->myint() != myint_defaultvalue) {
...

...
myint_ = myint_defaultvalue;
...

instead?

Ilyan
  • 175
  • 6