🔧

Go 言語における空の構造体(struct{})の活用テクニック

Go プログラミング

はじめに

Go 言語には空の構造体struct{})という、一見何もないように見える型があります。しかし、この空の構造体は実は非常に便利で、様々な場面で活用できる優れた機能なのです。

本記事では、以下の活用パターンについて詳しく解説します:

  • struct{}の基本的な特徴
  • ゴルーチン間でのシグナル送信
  • 集合(Set)のような使い方
  • 関数をグループ化するユーティリティ型

実際のコード例を見ながら、メモリ効率が良く、読みやすいコードを書くためのstruct{}活用テクニックをマスターしていきましょう。

1. 空の構造体(struct{})とは?

var s struct{}
fmt.Println(unsafe.Sizeof(s)) // 0

Go の仕様上、空の構造体はメモリサイズが0 バイトとなります。なぜなら、中身が何もないため、メモリ上に場所を確保する必要がないからです。

この「メモリを全く使わない」という特徴を活かすことで、値そのものではなく「存在」や「タイミング」だけを表現したい場合に、非常に効率的なプログラムを作ることができます。

2. ゴルーチン間の通知に使う

複数のゴルーチンが連携して作業する際、「処理が完了したよ」という合図を送りたい場面があります。そんな時にchan struct{}を使うと、データの送受信によるメモリ使用量を完全にゼロにできます。

func main() {
    wait := make(chan struct{})

    go func() {
        fmt.Println("バックグラウンド処理を開始します")
        // 何らかの処理...
        wait <- struct{}{} // 空の構造体を送信
    }()

    fmt.Println("処理の完了を待っています...")
    <-wait // 送信されるまで待機
    fmt.Println("処理が完了しました!")
}

別の方法:close()でチャネルを閉じる

func main() {
    wait := make(chan struct{})

    go func() {
        fmt.Println("バックグラウンド処理を開始します")
        // 何らかの処理...
        close(wait) // チャネルを閉じて完了を通知
    }()

    fmt.Println("処理の完了を待っています...")
    <-wait // 完了まで待機
    fmt.Println("処理が完了しました!")
}

ポイント:

  • chan struct{}を使うことで、メモリ使用量を最小限に抑えつつ、ゴルーチン間の通知が可能になります。
  • close(wait)でチャネルを閉じると、受信側は即座に処理を続行できます
  • チャネルの値は使わず、「閉じられた」という事実だけで通知を行います

※チャネルのclose()は一度だけ呼ぶこと(二重クローズはパニックを起こします)

3. 集合(Set)のように使う

Go には他の言語のような「Set 型」が標準では用意されていません。しかし、map[Key]struct{}という形で疑似的な Set を作ることができ、要素の存在確認を高速に行えます。

※集合(Set)とは、重複を許さない要素の集まりを表すデータ構造

// 訪問済みの都市を管理するSet
visited := make(map[string]struct{})

// 要素を追加
visited["tokyo"] = struct{}{}
visited["osaka"] = struct{}{}

// 存在確認
if _, ok := visited["tokyo"]; ok {
    fmt.Println("東京は既に訪問済みです")
}

// 要素数を確認
fmt.Printf("訪問済み都市数: %d\n", len(visited))

メリット

  • 値の部分(struct{})はメモリを使わないため、キーだけのメモリ消費

4. 関数をグループ化するユーティリティ型

関連する機能をまとめて整理したい時、空の構造体をメソッドだけを提供する型として使用します。

// ログ出力機能をまとめた型
type Logger struct{}

func (Logger) Info(msg string) {
    fmt.Printf("[INFO] %s\n", msg)
}

func (Logger) Error(msg string) {
    fmt.Printf("[ERROR] %s\n", msg)
}

func (Logger) Debug(msg string) {
    fmt.Printf("[DEBUG] %s\n", msg)
}

// 使用例
func main() {
    var log Logger
    log.Info("サーバーを起動しました")
    log.Error("接続エラーが発生しました")
}

メリット

  • 関連する機能を 1 つの型にまとめて整理できる
  • インスタンスはメモリを使わないため、気軽に作成可能
  • コードの可読性と保守性が向上する

まとめ

空の構造体(struct{})は、Go の「シンプルさ」を体現した機能であり、メモリ使用量を抑えるのでパフォーマンスにも貢献すると思います。