Protocol Buffers(proto3)でoptionalをどう扱うか

### 2021.4.14 追記
proto3で削除されたoptionalですがv3.15(experimentalオプションを利用する場合はv3.12)から正式に実装されたため、それ以降のバージョンを利用する場合は素直にoptionalを利用してもらうのがいいと思います！
https://github.com/protocolbuffers/protobuf/releases/tag/v3.15.0

---

Protocol Buffersはproto3で`required`と`optional`が削除されました。
そもそも削除された経緯に関しては、[@qsona](https://qiita.com/qsona)さんの[エントリー](https://qiita.com/qsona/items/22c5c987d431552bbfe0)にて、分かりやすくまとめて下さっています。


そこで課題になるのが、proto3において各フィールドは全てデフォルト値を持つため、デフォルト値が設定されたフィールドが利用側から
1. `意図的にセットされたデフォルト値と同様の値`
2. `存在しないためセットされなかったデフォルト値`
のどちらか判断できないということです。

つまりoptionalな値をどのように表現するかということです。

## optionalの表現方法
選択肢はいくつかありますが、[Stack Overflow](https://stackoverflow.com/questions/42622015/how-to-define-an-optional-field-in-protobuf-3?rq=1)の回答が参考になります。

1.別フィールドとして、明示的に有無を表現する

```proto:schema.proto
message User {
  int32 id = 1;
  bool has_name = 2; // nameの存在有無を表す
  string name = 3;   // 実際の値
}
```

```Java:Example(Java)
User user1 = User.newBuilder()
		.setHasName(true)
		.setName("Tom")
		.build();

User user2 = User.newBuilder()
		.setHasName(false) // デフォルト値がfalseであるため、実際にはセットも不要
		.build();

user1.getHasName(); // true
user1.getName();    // "Tom"

user2.getHasName(); // false
user2.getName();    // ""
```


2.oneofを利用する

```proto:schema.proto
message User {
  int32 id = 1;
  oneof name_optional {
    string name = 2; // 扱い値をoneofでラップする
  }
}
```

```Java:Example(Java)
User user1 = User.newBuilder()
		.setName("Tom")
		.build();
User user2 = User.newBuilder()
		.build();

user1.getNameOptionalCase(); // User.NameOptionalCase.NAME
user1.getName();             // "Tom"

user2.getNameOptionalCase(); // User.NameOptionalCase.NAMEOPTIONAL_NOT_SET
user2.getName();             // ""
```


3.`google/protobuf/wrappers.proto`を利用する

```proto:schema.proto
message User {
  int32 id = 1;
  google.protobuf.StringValue name = 2;
}
```

```Java:Example(Java)
User user1 = User.newBuilder()
		.setName(StringValue.newBuilder().setValue("Tom").build())
		.build();
User user2 = User.newBuilder()
		.build();

user1.hasName();            // true
user1.getName().getValue(); // "Tom"

user2.hasName();            // false
user2.getName().getValue(); // ""
```


## 考察
まず共通して言えるのは、どの選択肢を選んでも、optionalな値であることを強制できないということです。*1
従ってどの選択肢でも、`存在有無のチェック` → `値に対する処理`を手続き的に行う必要があります。

1は、記法は非常にシンプルですが、2つのフィールドの関係性をコンパイラでは保証できず、コメント等でサポートする必要があります。

2は、1と比べコンパイラレベルでフィールドの有無を表現できるのがメリットですが、複数フィールドが想定されているoneofからすると少し違和感のある使い方です。

3は、optionalな値を扱う上で直感的で使いやすいですが、いくつかデメリットもあります。

- wrapper用のBuilderが必要だったり、値を取り出すために`getValue()`を呼び出したりと若干冗長
- プリミティブでない値をoptionalにしたい場合、1か2と併用する必要があり、optionalの表現方法が2通りになり統一できない

例:

```proto:schema.proto
message User {
  int32 id = 1;
  Fullname fullname = 2;
}

message Fullname {
  string first_name = 1;
  string last_name = 2;
}
```

例えばこのようなケースではwrapperは以下の型しか提供されていないため、fullnameごとoptionalにしたい場合には利用できません。

```
DoubleValue
FloatValue
Int64Value
UInt64Value
Int32Value
UInt32Value
BoolValue
StringValue
BytesValue
```

従って、optional表現を統一したい場合には、1か2で統一するのが分かりやすいと思います。

*1: 言語によって実装様々ですが、Maybe、Option、Optional、null安全のような存在しない可能性を強制するもの

## まとめ
Protocol Buffersは仕様がコンパクトなスキーマ言語で、学習コストも低く可読性も良いのが特徴です。
しかしそれ故に、他ではよくあるような表現が取り除かれているものもあり、それらを適時カバーしながら使用することでより多くの表現ができると思います。