Gob

シリアライゼーションGoバイナリフォーマット標準ライブラリRPC

ライブラリ

Gob - Go言語のバイナリシリアライゼーションフォーマット

概要

Gobは、Go言語の標準ライブラリに含まれるバイナリシリアライゼーションフォーマットです。Go言語間でのデータ交換に特化して設計されており、型情報を含む自己記述的なフォーマットを採用しています。encoding/gobパッケージとして提供され、複雑なデータ構造(ポインタ、スライス、マップ、インターフェースなど)を効率的にシリアライズできます。特にRPCやネットワーク通信での使用を想定して最適化されています。

詳細

Gobは、Rob PikeとKen Thompsonによって設計された、Go言語専用のバイナリエンコーディング形式です。Protocol BuffersやMessagePackとは異なり、スキーマ定義を必要とせず、Go言語の型システムと密接に統合されています。データストリームに型情報を埋め込むことで、送信側と受信側で完全に同じ型定義を持つ必要がありません。

Gobの特徴的な設計として、ストリーミング対応があります。単一の接続で複数の値を順次送受信でき、型情報は最初の送信時のみ含まれるため、同じ型の複数の値を効率的に転送できます。また、フィールドの追加や削除に対してある程度の互換性を持ち、構造体の進化に対応できます。

エンコーディングは再帰的に行われ、ポインタの循環参照も適切に処理されます。また、インターフェース型の値もサポートしており、実際の型情報と共にシリアライズされます。ただし、関数、チャネル、unsafe.Pointerなど一部の型はシリアライズできません。

メリット・デメリット

メリット

  • Go言語との完全な統合: 標準ライブラリで提供され、追加依存なし
  • 自己記述的: スキーマ定義不要で、型情報を自動的に含む
  • ストリーミング対応: 複数の値を効率的に送受信可能
  • 型安全: コンパイル時の型チェックと実行時の型検証
  • ポインタ・循環参照対応: 複雑なデータ構造も正確にシリアライズ
  • バージョン互換性: フィールドの追加・削除にある程度対応

デメリット

  • Go言語専用: 他の言語との相互運用性なし
  • サイズ効率: 型情報を含むため、純粋なバイナリより大きい
  • パフォーマンス: JSONより高速だが、特化型シリアライザより低速
  • カスタマイズ性の制限: エンコーディング形式の変更不可
  • 一部型の非対応: 関数、チャネル等はシリアライズ不可
  • デバッグ困難: バイナリフォーマットのため人間が読めない

参考ページ

書き方の例

1. 基本的なエンコード・デコード

package main

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

type Person struct {
    Name    string
    Age     int
    Email   string
    Active  bool
}

func main() {
    // エンコード
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    
    person := Person{
        Name:   "田中太郎",
        Age:    30,
        Email:  "[email protected]",
        Active: true,
    }
    
    err := encoder.Encode(person)
    if err != nil {
        log.Fatal("エンコードエラー:", err)
    }
    
    // デコード
    decoder := gob.NewDecoder(&buf)
    var decodedPerson Person
    
    err = decoder.Decode(&decodedPerson)
    if err != nil {
        log.Fatal("デコードエラー:", err)
    }
    
    fmt.Printf("デコード結果: %+v\n", decodedPerson)
}

2. 複数の値のストリーミング

package main

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

type Message struct {
    ID        int
    Content   string
    Timestamp int64
}

func streamingExample() {
    var network bytes.Buffer // ネットワーク接続をシミュレート
    encoder := gob.NewEncoder(&network)
    decoder := gob.NewDecoder(&network)
    
    // 送信側: 複数のメッセージを送信
    messages := []Message{
        {ID: 1, Content: "最初のメッセージ", Timestamp: 1234567890},
        {ID: 2, Content: "2番目のメッセージ", Timestamp: 1234567891},
        {ID: 3, Content: "3番目のメッセージ", Timestamp: 1234567892},
    }
    
    for _, msg := range messages {
        if err := encoder.Encode(msg); err != nil {
            log.Fatal("送信エラー:", err)
        }
    }
    
    // 受信側: ストリームから読み取り
    for i := 0; i < len(messages); i++ {
        var received Message
        if err := decoder.Decode(&received); err != nil {
            log.Fatal("受信エラー:", err)
        }
        fmt.Printf("受信: %+v\n", received)
    }
}

3. インターフェース型のシリアライゼーション

package main

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

// インターフェース定義
type Animal interface {
    Speak() string
}

type Dog struct {
    Name  string
    Breed string
}

func (d Dog) Speak() string {
    return fmt.Sprintf("%s: ワンワン!", d.Name)
}

type Cat struct {
    Name  string
    Color string
}

func (c Cat) Speak() string {
    return fmt.Sprintf("%s: ニャー!", c.Name)
}

type Zoo struct {
    Animals []Animal
}

func interfaceExample() {
    // 型を登録(インターフェース使用時は必須)
    gob.Register(Dog{})
    gob.Register(Cat{})
    
    zoo := Zoo{
        Animals: []Animal{
            Dog{Name: "ポチ", Breed: "柴犬"},
            Cat{Name: "ミケ", Color: "三毛"},
            Dog{Name: "ハチ", Breed: "秋田犬"},
        },
    }
    
    // エンコード
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    if err := encoder.Encode(zoo); err != nil {
        panic(err)
    }
    
    // デコード
    var decodedZoo Zoo
    decoder := gob.NewDecoder(&buf)
    if err := decoder.Decode(&decodedZoo); err != nil {
        panic(err)
    }
    
    // インターフェースメソッドの呼び出し
    for _, animal := range decodedZoo.Animals {
        fmt.Println(animal.Speak())
    }
}

4. カスタムエンコーディング(GobEncoder/GobDecoder)

package main

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

// カスタム型
type CustomTime struct {
    time.Time
}

// GobEncoderインターフェースを実装
func (ct CustomTime) GobEncode() ([]byte, error) {
    return []byte(ct.Format(time.RFC3339)), nil
}

// GobDecoderインターフェースを実装
func (ct *CustomTime) GobDecode(data []byte) error {
    t, err := time.Parse(time.RFC3339, string(data))
    if err != nil {
        return err
    }
    ct.Time = t
    return nil
}

type Event struct {
    Name      string
    Timestamp CustomTime
}

func customEncodingExample() {
    event := Event{
        Name:      "システム起動",
        Timestamp: CustomTime{time.Now()},
    }
    
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    decoder := gob.NewDecoder(&buf)
    
    // エンコード(カスタムエンコーディングが使用される)
    if err := encoder.Encode(event); err != nil {
        panic(err)
    }
    
    var decoded Event
    if err := decoder.Decode(&decoded); err != nil {
        panic(err)
    }
    
    fmt.Printf("イベント: %s, 時刻: %s\n", 
        decoded.Name, decoded.Timestamp.Format("2006-01-02 15:04:05"))
}

5. 構造体の進化とバージョン互換性

package main

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

// バージョン1の構造体
type PersonV1 struct {
    Name string
    Age  int
}

// バージョン2の構造体(フィールド追加)
type PersonV2 struct {
    Name    string
    Age     int
    Email   string // 新しいフィールド
    Phone   string // 新しいフィールド
}

func versionCompatibility() {
    // V1でエンコード
    v1 := PersonV1{Name: "山田花子", Age: 25}
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    encoder.Encode(v1)
    
    // V2でデコード(新しいフィールドはゼロ値)
    var v2 PersonV2
    decoder := gob.NewDecoder(&buf)
    decoder.Decode(&v2)
    fmt.Printf("V1→V2: %+v\n", v2)
    
    // V2でエンコード
    buf.Reset()
    v2Full := PersonV2{
        Name:  "鈴木一郎",
        Age:   30,
        Email: "[email protected]",
        Phone: "090-1234-5678",
    }
    encoder = gob.NewEncoder(&buf)
    encoder.Encode(v2Full)
    
    // V1でデコード(余分なフィールドは無視)
    var v1Decoded PersonV1
    decoder = gob.NewDecoder(&buf)
    decoder.Decode(&v1Decoded)
    fmt.Printf("V2→V1: %+v\n", v1Decoded)
}

6. ファイルへの保存と読み込み

package main

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

type GameState struct {
    Level      int
    Score      int64
    PlayerName string
    Inventory  map[string]int
    Position   struct {
        X, Y, Z float64
    }
}

func saveToFile(filename string, state GameState) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    encoder := gob.NewEncoder(file)
    return encoder.Encode(state)
}

func loadFromFile(filename string) (GameState, error) {
    var state GameState
    
    file, err := os.Open(filename)
    if err != nil {
        return state, err
    }
    defer file.Close()
    
    decoder := gob.NewDecoder(file)
    err = decoder.Decode(&state)
    return state, err
}

func fileExample() {
    gameState := GameState{
        Level:      5,
        Score:      12500,
        PlayerName: "プレイヤー1",
        Inventory: map[string]int{
            "ポーション": 3,
            "エリクサー": 1,
            "鍵": 5,
        },
        Position: struct{ X, Y, Z float64 }{100.5, 50.0, -25.3},
    }
    
    // 保存
    if err := saveToFile("game.gob", gameState); err != nil {
        panic(err)
    }
    
    // 読み込み
    loaded, err := loadFromFile("game.gob")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("読み込んだゲーム状態: %+v\n", loaded)
}