GoとRustでProtocolBuffersとFlatBuffersのEnumの扱いについて

最初の動機はProtocolBuffersのEnum項目を未指定にした場合、Goでは0値として解釈されるが他の言語ではどうなるのか？という疑問からスタートしました。ついでにFlatBuffersも調べてみました。

結論から言うとProtocolBuffers、FlatBuffersいずれもGo、Rustにおいて未指定の場合は0値として解釈されます。

なおサンプルで使用したコードは[このリポジトリ](https://github.com/t10471/serializers)にあります。

## ProtocolBuffers

試すprotobufは下記です。

```protobuf
syntax = "proto3";

message Message {
    enum MessageType {
        Handshake = 0;
        SendTransaction = 1;
        SendBlock = 2;
        Bye = 3;
    }
    enum Status {
        OK = 0;
        NG = 1;
    }
    MessageType type = 1;
    Status status = 2;
    bytes payload = 3;
}
```

### Go

Goのコード生成は下記のコマンドで行います。protocのインストールは[この辺りの記事](https://qiita.com/tkmn0/items/7208c880693e63ca247a)を参考にしてください。

```sh
$ protoc --go_out=./p2p ./p2p.proto
```

#### 何も指定しない場合

```go
	m := &p2p.Message{}
	buf, _ := proto.Marshal(m)
	fmt.Printf("Serialize: %v\n", buf)
	msg := &p2p.Message{}
	_ = proto.Unmarshal(buf, msg)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", msg.Type)
```
下記の出力になります。そもそも、シリアライズデータが空になります。

```
Serialize: []
Deserialize:
Type: Handshake
```

#### 0値を指定した場合

```go
	m = &p2p.Message{Type: p2p.Message_Handshake}
	buf, _ = proto.Marshal(m)
	fmt.Printf("Serialize: %v\n", buf)
	msg = &p2p.Message{}
	_ = proto.Unmarshal(buf, msg)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", msg.Type)
```
結果は変わらず。どちらもシリアライズデータは空なので区別はできません。

```
Serialize: []
Deserialize:
Type: Handshake
```

#### 0値と他のフィールドで0値以外を指定した場合

```go
	m = &p2p.Message{Type: p2p.Message_Handshake, Status: p2p.Message_NG}
	buf, _ = proto.Marshal(m)
	fmt.Printf("Serialize: %v\n", buf)
	msg = &p2p.Message{}
	_ = proto.Unmarshal(buf, msg)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", msg.Type)
```
0値の箇所はやっぱり削られます。

```
Serialize: [16 1]
Deserialize: status:NG
Type: Handshake
```

#### 0値以外を指定した場合

```go
	m = &p2p.Message{Type: p2p.Message_Bye}
	buf, _ = proto.Marshal(m)
	fmt.Printf("Serialize: %v\n", buf)
	msg = &p2p.Message{}
	_ = proto.Unmarshal(buf, msg)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", msg.Type)
```
不安になったので別の値を指定してみました。もちろん、シリアライズされデコードできます。

```
Serialize: [8 3]
Deserialize: type:Bye
Type: Bye
```

### Rust

Rustの場合はbuid時にコード生成するライブラリがあるので、build.rsに下記を記述します。

```rust
extern crate protoc_rust;

use protoc_rust::Customize;

fn main() {
    protoc_rust::run(protoc_rust::Args {
        out_dir: "src",
        input: &["p2p.proto"],
        includes: &["."],
        customize: Customize {
            carllerche_bytes_for_bytes: Some(true),
            carllerche_bytes_for_string: Some(true),
            ..Default::default()
        },
    })
    .expect("protoc");
}
```

#### 何も指定しない場合

```rust
    let m = p2p::Message::new();
    let buf = Bytes::from(m.write_to_bytes().unwrap());
    println!("Serialize: {:?}", buf);
    let msg = parse_from_bytes::<p2p::Message>(&buf).unwrap();
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.get_field_type());
```
Goと一緒です。Optionがある言語とはいえ、シリアライズ結果が空ではどうにもならないですね。

```
Serialize: b""
Deserialize:
Type: Handshake
```

#### 0値を指定した場合

```rust
    let mut m = p2p::Message::new();
    m.set_field_type(p2p::Message_MessageType::Handshake);
    let buf = Bytes::from(m.write_to_bytes().unwrap());
    println!("Serialize: {:?}", buf);
    let msg = parse_from_bytes::<p2p::Message>(&buf).unwrap();
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.get_field_type());
```
結果は一緒なので、もはやライブの使い方ですが、`set_field_type`で `type`フィールドに値を設定します。OOPみたいでRustっぽくない気もします。
 
```
Serialize: b""
Deserialize:
Type: Handshake
```

#### 0値と他のフィールドで0値以外を指定した場合

```rust
    let mut m = p2p::Message::new();
    m.set_field_type(p2p::Message_MessageType::Handshake);
    m.set_status(p2p::Message_Status::NG);
    let buf = Bytes::from(m.write_to_bytes().unwrap());
    println!("Serialize: {:?}", buf);
    let msg = parse_from_bytes::<p2p::Message>(&buf).unwrap();
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.get_field_type());
```
Goと変わらず。

```
Serialize: b"\x10\x01"
Deserialize: status: NG
Type: Handshake
```

#### 0値以外を指定した場合

```rust
    let mut m = p2p::Message::new();
    m.set_field_type(p2p::Message_MessageType::Bye);
    let buf = Bytes::from(m.write_to_bytes().unwrap());
    println!("Serialize: {:?}", buf);
    let msg = parse_from_bytes::<p2p::Message>(&buf).unwrap();
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.get_field_type());
```
やっぱり、Goと変わらず。

```
Serialize: b"\x08\x03"
Deserialize: type: Bye
Type: Bye
```

## FlatBuffers

`flatc --proto ../proto/p2p.proto`でprotobufファイルから生成できるのですが、生成されるfbsファイルのnamespaceの使い方が微妙です。enumとtableでnamespaceが分断されて、package名が無いGoプログラムになりました。。。なので今回は手で記述しました。(protobufの記述を直せばちゃんと出力される?)

```
namespace P2P;

enum MessageType : int { Handshake = 0, SendTransaction, SendBlock, Bye }
enum Status : int { OK = 0, NG }

table Message {
    type:MessageType;
    status:Status;
    payload:[byte];
}

xroot_type Message;
```

### Go

Goのコード生成は下記のコマンドです。faltcのインストールは[この辺りの記事](https://rwinslow.com/posts/how-to-install-flatbuffers/)を参考にしてください。

```sh
$ flatc -g p2p.fbs
```
Goの場合は下記のように複数ファイルが生成されます。

```
├── P2P
│   ├── Message.go
│   ├── MessageType.go
│   └── Status.go
```

#### 何も指定しない場合
```go
	builder := flatbuffers.NewBuilder(0)
	P2P.MessageStart(builder)
	m := P2P.MessageEnd(builder)
	builder.Finish(m)
	b := builder.FinishedBytes()
	fmt.Printf("Serialize: %v\n", b)
	msg := P2P.GetRootAsMessage(b, 0)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
typeは未指定でも0値が設定されます。
ProtocolBuffersとは違って、高速にパースするために値のoffset情報やvtable情報が入っているので空では無いです。ただし、結局は未指定の場合、フィールドは切り詰められます。

```
Serialize: [8 0 0 0 4 0 4 0 4 0 0 0]
Deserialize: &{{[8 0 0 0 4 0 4 0 4 0 0 0] 8}}
Type: Handshake
```

#### 0値を指定した場合

```go
	builder = flatbuffers.NewBuilder(0)
	P2P.MessageStart(builder)
	P2P.MessageAddType(builder, P2P.MessageTypeHandshake)
	m = P2P.MessageEnd(builder)
	builder.Finish(m)
	b = builder.FinishedBytes()
	fmt.Printf("Serialize: %v\n", b)
	msg = P2P.GetRootAsMessage(b, 0)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
上に書いたように同じbyteが出力されるので、0値を指定してもフィールドは切り詰められます。

```
Serialize: [8 0 0 0 4 0 4 0 4 0 0 0]
Deserialize: &{{[8 0 0 0 4 0 4 0 4 0 0 0] 8}}
Type: Handshake
```
#### 0値と他のフィールドで0値以外を指定した場合

```go
	builder = flatbuffers.NewBuilder(0)
	P2P.MessageStart(builder)
	P2P.MessageAddType(builder, P2P.MessageTypeHandshake)
	P2P.MessageAddStatus(builder, P2P.StatusNG)
	m = P2P.MessageEnd(builder)
	builder.Finish(m)
	b = builder.FinishedBytes()
	fmt.Printf("Serialize: %v\n", b)
	msg = P2P.GetRootAsMessage(b, 0)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
byte列が長くなってますが、0値は切り詰められます。

```
Serialize: [12 0 0 0 8 0 8 0 0 0 4 0 8 0 0 0 1 0 0 0]
Deserialize: &{{[12 0 0 0 8 0 8 0 0 0 4 0 8 0 0 0 1 0 0 0] 12}}
Type: Handshake
```

#### 0値以外を指定した場合

```go
	builder = flatbuffers.NewBuilder(0)
	P2P.MessageStart(builder)
	P2P.MessageAddType(builder, P2P.MessageTypeBye)
	m = P2P.MessageEnd(builder)
	builder.Finish(m)
	b = builder.FinishedBytes()
	fmt.Printf("Serialize: %v\n", b)
	msg = P2P.GetRootAsMessage(b, 0)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
byte列は上のものと同じ長さです。ちゃんと、0値ではない値が取得できます。

```
Serialize: [12 0 0 0 0 0 6 0 8 0 4 0 6 0 0 0 3 0 0 0]
Deserialize: &{{[12 0 0 0 0 0 6 0 8 0 4 0 6 0 0 0 3 0 0 0] 12}}
Type: Bye
```

### Rust

Rustの場合はprotbuf同様、buid時にコード生成するライブラリがあるのでbuild.rsに下記を記述します。

```rust
extern crate flatc_rust;

use std::path::Path;

fn main() {
    flatc_rust::run(flatc_rust::Args {
        inputs: &[Path::new("./p2p.fbs")],
        out_dir: Path::new("./src/"),
        ..Default::default()
    }).expect("flatc");
}
```

#### 何も指定しない場合

```rust
    let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(0);
    let m = Message::create(&mut builder, &Default::default());
    builder.finish(m, None);
    let buf = builder.finished_data();
    println!("Serialize: {:?}", buf);
    let msg = get_root_as_message(buf);
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.type_());
```
Rustの場合はSerializeにデフォルト値を指定しないといけません。。。
とはいえ出力されるbyte列はGoと一緒なのでフィールドは切り詰められます。

```
Serialize: [8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0]
Deserialize: Message { _tab: Table { buf: [8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0], loc: 8 } }
Type: Handshake
```

#### 0値を指定した場合

```rust
    let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(0);
    let m = Message::create(
        &mut builder,
        &MessageArgs {
            type_: MessageType::Handshake,
            ..Default::default()
        },
    );
    builder.finish(m, None);
    let buf = builder.finished_data();
    println!("Serialize: {:?}", buf);
    let msg = get_root_as_message(buf);
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.type_());
```
Goと変わらず。

```
Serialize: [8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0]
Deserialize: Message { _tab: Table { buf: [8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0], loc: 8 } }
Type: Handshake

```

#### 0値と他のフィールドで0値以外を指定した場合

```rust
    let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(0);
    let m = Message::create(
        &mut builder,
        &MessageArgs {
            type_: MessageType::Handshake,
            status: Status::NG,
            ..Default::default()
        },
    );
    builder.finish(m, None);
    let buf = builder.finished_data();
    println!("Serialize: {:?}", buf);
    let msg = get_root_as_message(buf);
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.type_());
```
Goと変わらず。

```
Serialize: [12, 0, 0, 0, 8, 0, 8, 0, 0, 0, 4, 0, 8, 0, 0, 0, 1, 0, 0, 0]
Deserialize: Message { _tab: Table { buf: [12, 0, 0, 0, 8, 0, 8, 0, 0, 0, 4, 0, 8, 0, 0, 0, 1, 0, 0, 0], loc: 12 } }
Type: Handshake
```

#### 0値以外を指定した場合

```rust
    let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(0);
    let m = Message::create(
        &mut builder,
        &MessageArgs {
            type_: MessageType::Bye,
            ..Default::default()
        },
    );
    builder.finish(m, None);
    let buf = builder.finished_data();
    println!("Serialize: {:?}", buf);
    let msg = get_root_as_message(buf);
    println!("Deserialize: {:?}", msg);
    println!("Type: {:?}", msg.type_());
```
もちろん、Goと変わらず。

```
Serialize: [12, 0, 0, 0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0, 3, 0, 0, 0]
Deserialize: Message { _tab: Table { buf: [12, 0, 0, 0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0, 3, 0, 0, 0], loc: 12 } }
Type: Bye
```

## 結論

結局、ProtocolBuffersもFlatBuffersもシリアライズ時に未指定・0値のものは切り詰められるので、デシリアライズ時は0値で復元されるということです。

## おまけ FlatBuffersのシリアライズデータを覗く

[この記事](https://github.com/mzaks/FlatBuffersSwift/wiki/FlatBuffers-Explained)が一番分かりやすかったのですが、理解するまで時間かかったのでここに書いておきます。

空の方がわかりにくいので先に値があるパターンを説明します。

```go
	builder = flatbuffers.NewBuilder(0)
	P2P.MessageStart(builder)
	P2P.MessageAddType(builder, P2P.MessageTypeSendBlock)
	P2P.MessageAddStatus(builder, P2P.StatusNG)
	m = P2P.MessageEnd(builder)
	builder.Finish(m)
	b = builder.FinishedBytes()
	fmt.Printf("Serialize: %v\n", b)
	msg = P2P.GetRootAsMessage(b, 0)
	fmt.Printf("Deserialize: %v\n", msg)
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
	fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Status()])
```

```
Serialize: [12 0 0 0 8 0 12 0 8 0 4 0 8 0 0 0 1 0 0 0 2 0 0 0]
Deserialize: &{{[12 0 0 0 8 0 12 0 8 0 4 0 8 0 0 0 1 0 0 0 2 0 0 0] 12}}
Type: SendBlock
Type: NG
```

stringが無いので元記事よりも単純な構造になっています。

```
// root table offset
1. 12 0 0 0 // root tableの開始offset 12バイト先なので、6行目を示している
//vtable info
2.  8 0     // vtable infoのsize
3. 12 0     // data tableのsize
4.  8 0     // typeフィールドのoffset 8行目を示している
5.  4 0     // statusフィールドのoffset 7行目を示している 
// data table
6.  8 0 0 0 // vtable infoの開始が8offset前であることを示している つまり2行目
7.  1 0 0 0 // statusの値 (NG)
8.  2 0 0 0 // typeの値 (SendBlock)
```

1行目のroot tableのoffsetから6行目に飛んで、そこから2行目に戻って各フィールドのoffsetを取得するというのがフィールドの値の取得方法？生成されるプログラムはoffsetが各フィールドごとにハードコーディングされています。

```go
func (rcv *Message) Type() MessageType {
	o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
	if o != 0 {
		return rcv._tab.GetInt32(o + rcv._tab.Pos)
	}
	return 0
}
```

フィールドが全部0値の場合は下記になります。12byteですが、フィールドの情報は存在しません。
その場合、上のメソッドは 変数`o` が0となり、`return 0` を返すという処理になります。

```
// root table offset
1.  8 0 0 0 root table offset (4行目の位置を示している)
//vtable info
2.  4 0  // vtable infoのsize
3.  4 0  // data tableのsize
// data table
4.  4 0 0 0 // vtable infoの開始が4offset前であることを示している つまり2行目
```

## 追記
じゃあ、typeしか指定しない場合とstatusしか指定しない場合はどうなんだろうというの書いたあとに気になったので調べてみました。

### typeのみ

```go
  builder = flatbuffers.NewBuilder(0)
  P2P.MessageStart(builder)
  P2P.MessageAddType(builder, P2P.MessageTypeBye)
  m = P2P.MessageEnd(builder)
  builder.Finish(m)
  b = builder.FinishedBytes()
  fmt.Printf("Serialize: %v\n", b)
  msg = P2P.GetRootAsMessage(b, 0)
  fmt.Printf("Deserialize: %v\n", msg)
  fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
typeしか指定していない場合、最初のところでアライメントの調整が入っています。

```
// root table offset
12 0 0 0 0 0 
//vtable info
 6 0 
 8 0 
 4 0 // ハードコーディングのoffsetはここを示す
// data table
 6 0 0 0 
 3 0 0 0 // 結局ここの値が返る
```

### statusのみ

```go
  builder = flatbuffers.NewBuilder(0)
  P2P.MessageStart(builder)
  P2P.MessageAddStatus(builder, P2P.StatusNG)
  m = P2P.MessageEnd(builder)
  builder.Finish(m)
  b = builder.FinishedBytes()
  fmt.Printf("Serialize: %v\n", b)
  msg = P2P.GetRootAsMessage(b, 0)
  fmt.Printf("Deserialize: %v\n", msg)
  fmt.Printf("Type: %v\n", P2P.EnumNamesMessageType[msg.Type()])
```
statusだけ指定した場合vtableは伸びますが、typeの場所は0になっています。

```
// root table offset
12 0 0 0 
//vtable info
 8 0 
 8 0 
 0 0  // ハードコーディングのoffsetはここを示しているが0なのでdata talbeは見ない
 4 0 
// data table
 8 0 0 0 
 1 0 0 0
```

プログラム的には値のvtableにあるdata tableのoffset情報を取得->data tableの値取得となっています。

```go
func (rcv *Message) Type() MessageType {
	o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
	if o != 0 {
		return rcv._tab.GetInt32(o + rcv._tab.Pos)
	}
	return 0
}
```

ついでにライブラリの方まで追うと、root table offset(t.Pos)からvtableの場所を求め
`t.GetVOffsetT(vtable)`でvtalbeのサイズを調べてオーバーしていなければ、
`t.GetVOffsetT(vtable + UOffsetT(vtableOffset))`でvtable内のハードコーディングされた値のoffsetからdata tableのoffsetを求めています。

```go
func (t *Table) Offset(vtableOffset VOffsetT) VOffsetT {
	vtable := UOffsetT(SOffsetT(t.Pos) - t.GetSOffsetT(t.Pos))
	if vtableOffset < t.GetVOffsetT(vtable) {
		return t.GetVOffsetT(vtable + UOffsetT(vtableOffset))
	}
	return 0
}
func (t *Table) GetSOffsetT(off UOffsetT) SOffsetT {
	return GetSOffsetT(t.Bytes[off:])
}
```