encoding/gobでinterface{}をシリアライズする

## 概要

[encoding/gob](https://golang.org/pkg/encoding/gob/)について調べてみました。interface{}はencoding/gobでシリアライズしてバイト列にできます。

## Gobとは

Go言語専用のシリアライズ形式です。シリアライズ形式というと、バイナリフォーマットで多言語からつかえるProtocol buffersや、RubyのMarshal.dumpで出力されるもの、テキストフォーマットだとJSONやXMLなどが思い当たります。
GobはRPCで通信するときに利用することが想定されていますが、計算途中の結果をファイルに出力するような場合も使えます。また、他のシリアライズ形式と違いGo専用なので、Goで使うなら簡単に使えるという利点があります。

## Gobにできて、Protocol buffersにできないこと
参考：[Gobs of data](https://blog.golang.org/gobs-of-data)

たとえば、Protocol buffersはself-describingでないため、送るデータとは別に構造の定義をDecodeしたいプログラムが知っている必要があります。それ以外に、次のような設計上の違いがあります。

* Top levelのintや、arrayを送ることができない、送るデータは構造体に入れる必要がある
* 途中でフィールドの変更をすることが難しい。厳密にEncodeとDecode側の構造が一致していないと読めない。
* デフォルト値がある、フィールドが無い状態(=nil)にできない。

要するにGoの変数なら、underlying typeでもstructでも、nil(=送らないか明にnilとする)やDynamicな型も含めてそのまま伝えられるという利点があります。ただし、他の言語や人間が読み取る用途には（やればできるでしょうが）向いてません。

## Gobのフォーマット

[doc.go](https://golang.org/src/encoding/gob/doc.go)に詳細はありますが、このような形式です。

~~~
(byteCount (-type id, encoding of a wireType)* (type id, encoding of a value))*
~~~

wireTypeはstructなどのフォーマットを伝えるための構造です。gobが受け取る新しい構造ごとにtype idが割当たります。その後に`type id`と実際の値のエンコードが続きます。

~~~go
type wireType struct {
  ArrayT           *arrayType
  SliceT           *sliceType
  StructT          *structType
  MapT             *mapType
  GobEncoderT      *gobEncoderType
  BinaryMarshalerT *gobEncoderType
  TextMarshalerT   *gobEncoderType
}
~~~

例えば、次のようなデータをエンコードすると、

~~~go:struct
type Test struct {
  Value interface{}
}
~~~

~~~go:value
  var test Test
  test.Value = "test"
~~~

こうなります。

~~~:encoded
00000000  1c ff 85 03 01 01 04 54  65 73 74 01 ff 86 00 01  |.......Test.....|
00000010  01 01 05 56 61 6c 75 65  01 10 00 00 00 13 ff 86  |...Value........|
00000020  01 06 73 74 72 69 6e 67  0c 06 00 04 74 65 73 74  |..string....test|
00000030  00                                                |.|
~~~

なお、struct自体の情報はこの構造で表します。

~~~go
// Struct type
type fieldType struct {
  Name string
  Id   typeId
}

type structType struct {
  CommonType
  Field []*fieldType
}
~~~

順番に意味を説明すると、次のようになります。

値|意味
----|----
1c |全体のサイズ
ff 85|-typeid = -67
03|wireTypeの3つめのフィールドStructTの
01|1つめのフィールドCommonType
01 04 54 65 73 74|フィールド名が 4文字の"Test"
01 ff 86|typeidが67
00|CommonTypeここまで
01|[]Field
01|は1要素
01 05 56 61 6c 75 65| Nameは5文字の"Value
01 10| typeid = 8 interface{} の値
00 00 00| wireTypeここまで
13 ff 86| 0x13サイズの typeid=67、Testのエンコード値
01| Valueフィールドは
06 73 74 72 69 6e 67| "string"
0c| typeid = 6 string  
06 00 04 74 65 73 74| 6byte delta 0 (=singleton value) "test"
00| Testここまで

`"test"`のようにstructでないものがTop levelにある場合は次のようになります。

~~~
(byteCount (type id, zero, encoding of a value))
~~~

`"test"`のエンコード結果は、次のようになります。

~~~:encoded
00000000  07 0c 00 04 74 65 73 74                           |....test|
~~~

値|意味
----|----
07|byteCount
0c|typeid = 6 string  
00|delta 0
04 74 65 73 74|"test"



## interface{}を送る

`interface{}`を値としてエンコードすると、`interface{}`だったという情報が落ちます。すると、デコードするときにこんなエラーが出ます。

~~~
gob: local interface type *interface {} can only be decoded from remote interface type; received concrete type int
~~~

`*interface{}`をエンコードして、`*interface{}`で受ければ、OKです。

## Register()の使いみち

`interface{}`メンバーに入った構造体を贈りたい場合があります。エンコードのときにこんなエラーが出ます。

~~~
gob: type not registered for interface: main.SubType
~~~

structは前述のようにフィールドをNameとtypeidでしか管理していないため、structが入れ子になっている場合、入れ子の子供側の構造体を`Register()`しないとEncodeされません。
ここはなぜこんな実装になっているか、あまり理解していないです。再帰的に見に行くとキリがない場合が有るのかも？

## その他、ありがちなエラー

Encode, Decodeの引数はどんな型でも取れるようになっているので、コンパイル時のチェックが効きません。思った挙動にならない、例えば、Decodeすると`nil`になるような場合は戻り値のエラーをprintするといいです。

~~~
gob: type mismatch in decoder: want struct type main.Data; got non-struct
~~~

この場合は、エンコードしたときの構造体を渡してデコードする必要があります。

~~~
attempt to decode into a non-pointer
DecodeValue of unassignable value
~~~

Decodeの引数は領域が割当たっているポインタじゃないとデコードしてくれません。


## おわりに

このエントリを調べるきっかけは、[transparentというライブラリでの2 phase commit](https://github.com/juntaki/transparent/tree/master/twopc)の実装です。まだあまり作り込んでませんが、gRPCでgobのバイト列をやり取りする実装になっています。golangの値の受け渡しに使うなら`encoding/gob`が自由度が高く簡単なので、おすすめです。