データのやりとりに gob を使う

Go 言語ではデータ構造のシリアライズのために標準でいくつかのパッケージが用意されています。

* `encoding/csv`
* `encoding/json`
* `encoding/xml`

等がそれですが、このように言語から完全に独立した汎用形式では表現できない構造をやりとりしたいときがあります。そこで使われるのが `encoding/gob` です。

# gob って？

* [Gobs of data - The Go Blog](https://blog.golang.org/gobs-of-data)

上の記事にもう全部書いてあるのですが、gob の特徴として挙げられている以下のような点です。

1. とくにかく簡単に使えること  
  データ構造さえ自明であれば、他のいかなる情報も encode / decode に必要ありません。`encode/json` でいう、`json:"hoge"` タグなどは必要ないのです。
2. 高効率であること  
  XML や JSON のような、テキストベースの形式は冗長すぎます。gob はバイナリーなのでネットワーク間のやりとりも高速です。  
  [最新のベンチマーク][goserialize]を見る限り、encode / decode の速度もまあまあ優秀です。
3. 自己記述的であること  
  encode したデータそれ自体で decode のために必要な情報は全てまかなえます。ファイルに保存しておいて何年も後に取り出すとしても、なんの問題もありません。

  [goserialize]: https://github.com/alecthomas/go_serialization_benchmarks
[shibu]: http://qiita.com/shibukawa/items/878c5fe8ec09935fccd2

まさに Google 自身が同種の用途のために開発した Protocol Buffers というデータ形式があります。先の記事には gob のそれに対する優位性について詳しく述べられていますが、僕は Protocol Buffers を使ったことないのでここでは割愛します。

# 使ってみる

## 単純な構造体を encode / decode する

利用法は `encoding/json` 等とほとんど変わりません。ただ、先にも述べたように `json:"hoge"` タグ等の定義が完全に不要なため、それよりは簡潔です。

* 以下、簡便のためエラー処理は端折ります。
* 以下のコードは[ここ](https://play.golang.org/p/6ti0J7-HwZ)で実行出来ます。

```go
// 実行すると以下のように表示される。
// encoded: 46 bytes
// decoded: &{F1:hoge F2:123}
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
)

type Hoge struct {
	F1 string
	F2 int64
}

func main() {
	encoded := encode()
	fmt.Printf("encoded: %d bytes\n", len(encoded))

	decoded := decode(encoded)
	fmt.Printf("decoded: %+v\n", decoded)
}

func encode() []byte {
	h := Hoge{F1: "hoge", F2: 123}
	buf := bytes.NewBuffer(nil)
	_ = gob.NewEncoder(buf).Encode(&h)
	return buf.Bytes()
}

func decode(data []byte) *Hoge {
	var h Hoge
	buf := bytes.NewBuffer(data)
	_ = gob.NewDecoder(buf).Decode(&h)
	return &h
}
```

特に説明は必要ありませんね。`encoding/json` と違うところは `json.Marshal() / json.Unmarshal()` のような便利メソッドが存在しないことです。

## 独自の encoder / decoder を定義する

private fields みたいな通常 encode 出来ないヤツは `GobEncoder / GobDecoder` interface を使います。`json.Marshaler / json.Unmarshaler` と同じです。

* ここでは encoder / decoder の定義のために “Alias” テクニックを使っています。
  - [Custom JSON Marshalling in Go · Choly's Blog](http://choly.ca/post/go-json-marshalling/)
* 以下のコードは[ここ](https://play.golang.org/p/WQ_oO4hkc6)で実行出来ます。

```go
type Hoge struct {
	F1 string
	F2 int64
	f3 int64 // private fields なので通常の Encoder ではこれが見えない
}

func (h *Hoge) GobEncode() ([]byte, error) {
	buf := bytes.NewBuffer(nil)
	// Alias 型に F3 というダミーのフィールドを作ってそこに格納する
	type Alias Hoge
	_ = gob.NewEncoder(buf).Encode(struct {
		F3 int64
		*Alias
	}{F3: h.f3, Alias: (*Alias)(h)})
	return buf.Bytes(), nil
}

func (h *Hoge) GobDecode(data []byte) error {
	buf := bytes.NewBuffer(data)
	// 同じく F3 というダミーのフィールドを作って、そこに値が入る
	type Alias Hoge
	aux := struct {
		F3 int64
		*Alias
	}{Alias: (*Alias)(h)}
	_ = gob.NewDecoder(buf).Decode(&aux)
	// ダミーのフィールドから転記する
	h.f3 = aux.F3
	return nil
}
```

## interface を encode / decode する

今までの例だと JSON / XML を使ったときと余り違いが分かりません。gob の優れている点は interface の扱いです。JSON 等への encode ではそもそも型の情報が抜けてしまうため encode 自体が不可能な場合があります（複雑な Marshaller を定義すれば別ですが）。

```go
type Hoge struct {
	Walkers []Walker
}

type Walker interface {
	Walk()
}

type Cat string

func (c Cat) Walk() { fmt.Printf("a cat %s is walking...\n", c) }

type Dog string

func (d Dog) Walk() { fmt.Printf("a dog %s is walking...\n", d) }
```

`Hoge` は `Walker` という interface を持った何らかの実体を中に収めた構造体です。これを `encoding/json` を使って encode / decode しようとしてもうまく行きません。

* コード例は[こちら][json]。

[json]: https://play.golang.org/p/ljUuhn-wk4

```go
// Walker なんてよく分からないものは無理です！的なエラー
panic: json: cannot unmarshal string into Go struct field Hoge.Walkers of type main.Walker
```

こんなときにこそ gob ……なのですが、流石にノーヒントでは無理です。[encoding/json のコード例][json]を [`s/json/gob/g` しただけ][gob1]だと以下のようなエラーを吐いて止まってしまいます。

[gob1]: https://play.golang.org/p/HIJzF6rIz4

```go
// Cat ってヤツのヒントください！的なエラー
panic: gob: type not registered for interface: main.Cat
```

ヒントは `gob.Register()` という関数で与えることが出来ます。例えば以下のような感じです。

```go
gob.Register(Cat(""))
gob.Register(Dog(""))
```

ヒントを与えた[完全な例][gob2]では以下のように encode / decode がうまく行ったことが確認できます。

[gob2]: https://play.golang.org/p/jIURoXCX70

```go
original: main.Hoge{Walkers:[]main.Walker{"タマ", "ポチ"}}
decoded:  main.Hoge{Walkers:[]main.Walker{"タマ", "ポチ"}}
// 各の Walk() を呼び出してみると型に応じて文章が変わっている
a cat タマ is walking...
a dog ポチ is walking...
```

きちんと型が認識されていることが分かりますね。interface をそのまま扱えるのは gob の大きな利点です。

# まとめ（どういう場合に使う？）

多少の手間（`gob.Register()` によるヒント）は必要ですが、Go のデータ構造をそのままやりとりできるのは大きな魅力です。例えばマイクロサービス同士でデータを交換したいけど、JSON 等の汎用的な形式で間に合わない場合に良いでしょう。

少々ニッチな用途ですが、GAE/Go ではこれがかなり有用です。なんとならば、GAE/Go では [GOMAXPROCS が 1 のため][gomaxprocs]、CPU bound な処理を並列化できないのです。このとき各処理を別の endpoint で動作可能にし、必要なデータを gob でやりとりすることで無理矢理並列化できます。具体的に何をどうやったのかは又別の記事にまとめたいと思います。

[gomaxprocs]: http://qiita.com/hogedigo/items/f0f409ee944c4b2107c3