🪁

Go の空構造体と向き合う

2022/03/09に公開

Go における空構造体とは、フィールドを一つも持たない構造体のことです。それについて学んだことがあったのでメモ代わりに書きます。

主な特徴は以下の通りです。

  • メモリ効率:空構造体はメモリを全く使用しません。これは、Go がデータを格納する際に必要なメモリスペースを割り当てないためです。
  • インスタンスの一意性:空構造体は状態を持たないため、代入されたインスタンスは同一のものとみなされ、同じアドレスを示します。
package main

import "fmt"

type (
	number  struct{}
	pointer struct{}
)

func (n number) callTen() int {
	return 10
}

func (p pointer) callAddress() string {
	return fmt.Sprintf("%p\n", &p)
}

var (
	numberFirst = number{}
	p1 = pointer{}
	p2 = pointer{}
	p3 = pointer{}
)

func main() {
	// numberFirst から callAddress() は呼べない
	// p1~3 から callTen() は呼べない
	fmt.Println(numberFirst.callTen())
	fmt.Println(p1.callAddress(), p2.callAddress(), p3.callAddress())
}

// 出力
10
0x596c18
0x596c18
0x596c18

空構造体はいくつ生成しても同じアドレスを示しているので、実体 numberFirstp1p2p3 は同じものとして扱われていることがわかります。(numberFirst も同じアドレスを出力します)
また、Goでは type 句によって型の別名を付与すると完全に別の型として扱われるため、同じアドレスを示すものでも別名さえ付けてしまえば上記のようにそれぞれに個別にメソッド定義もできます

空構造体の活用方法

空構造体は、そのメモリ効率の良さなどから様々な場面で有用そうです。

1. チャネルのシグナル

まず、チャネルでシグナルを送信する際に使用できます。
例えば以下のように、ゴルーチンが処理を完了したことを伝えるために空構造体を送信することができます。(もちろんその時にメモリ消費はしません)

done := make(chan struct{})

go func() {
	// 何らかの処理
	
	done <- struct{}{}
}()

// シグナルの待機
<-done

2. ユニークなセットの表現

他にも、マップのキーとして空構造体を利用することで、ユニークなセットを表現できます。これにより、値を格納せずにキーのみをセットできます。

setMap := make(map[string]struct{})
setMap["item1"] = struct{}{}
setMap["item2"] = struct{}{}

// キーの存在確認をし、特定の処理を行える
if _, exists := setMap["item1"]; exists {
    // 何らかの処理
}

3. 戻り値の代替

関数やメソッドが戻り値を必要としない場合に、空構造体を使用することで意味のないデータの生成(メモリ消費)を避けられます。

func doSomething() struct{} {
	
	// 何らかの処理

	return struct{}{}
}

空構造体はより効率的な Go プログラミングに何かと活用できそうですね。

Discussion