まくろぐ
更新: / 作成:

.proto のフィールドを省略可能にする (option)

Protocol Buffers(.proto ファイル)のフィールド定義で optional というラベルを付けると、そのフィールドへの値のセットがオプショナルであることを示すことができます。 optional ラベルは、protoc コンパイラの v3.15.0 以降で使用できます。

sample.proto
syntax = "proto3";

option go_package = "github.com/maku77/myapp/pb";

message SampleMessage {
  string message = 1;  // 通常のフィールド
  optional string description = 2;  // 省略可能なフィールド
}

例えば、上記のような .proto ファイルから、次のように Golang コードを生成すると、

$ protoc --go_out=. --go_opt=paths=source_relative sample.proto

次のような SampleMessage 型のコードが生成されます。

sample.pb.go
type SampleMessage struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message     string  `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`               // 通常のフィールド
	Description *string `protobuf:"bytes,2,opt,name=description,proto3,oneof" json:"description,omitempty"` // 省略可能なフィールド
}

Golang の場合は、オプショナルな string フィールドは、*string として表現されるようですね。

データの受信側で、実際にフィールドに値がセットされているかを調べる方法は、各言語の protobuf ライブラリによって異なります。 例えば、C++ の場合は、has_description() というメソッドで description フィールドに値がセットされているかどうかを調べることができます。 これは、oneof フィールド に値がセットされていることを調べる方法と同様です。

各言語での optional フィールドの存在確認
if (m.has_foo()) {...}  // C++ の場合
if (m.HasFoo) {...}     // C# の場合
if m.Foo != nil {...}   // Go の場合
if (m.hasFoo()) {...}   // Java の場合
if (m.hasFoo()) {...}   // Javascript の場合
if m.HasField('foo'):   // Python の場合
if m.has_foo?           // Ruby の場合

(おまけ)デフォルト値と Field presence

Protocol Buffers では、バイナリ化したデータ形式のこと wire 形式 と読んでいます。 例えば、ファイルへの保存時や、gRPC のリクエスト送信時に使われるときのデータ形式です。 Protocol Buffers では、フィールドの型ごとに下記のようなデフォルト値が定義されており、データを wire 形式にシリアライズするときに、これらの デフォルト値を保存しない ようになっています(ネットワークなどで送信されません)。

  • 数値型: 0
  • bool 型: false
  • enum 型: 0 を持つ enum 値(つまり、先頭の XXXX_UNSPECIFIED
  • string 型、bytes 型、repeated ラベルの付いた型: 長さが 0 のデータ
  • message 型: 各言語の null に相当するデータ

これは、データサイズを削減するための proto3 の仕様です。 データの受信側では、フィールドに対応する値が入っていなければ、デフォルト値がセットされているものとして振る舞います。 この仕様は、データの送信側がフィールドの値を明示的にセットしなかった場合に問題になります。 例えば、送信側がある数値型のフィールドをセットしなかった場合、受信側はそのフィールドにデフォルト値の 0 が格納されていると判断しますが、意図的に 0 がセットされたのか、値がセットされなかったのかを区別できません。

そこで、値がセットされない可能性があるフィールドには optional ラベルを付加することで、wire 形式のデータに、フィールドの値の有無を示す情報 を付加できるようにしています。 このようなフィールドのことを、Explicit Presence と呼んでいます。 明示的に値の存在を示す、ということですね。 逆に、通常のフィールドでは、このような値の存在を示す情報は付加されないので、No Presence と呼ばれています。

options ラベルの付いたフィールドと、通常のフィールドの本質的な違いは、このような Field presence 情報の扱いだけです。

関連記事

まくろぐ
サイトマップまくへのメッセージ