付録A Go言語のまとめ
武舎 広幸
付録Aと付録Bは日本語版オリジナルの内容です。付録Aでは、この本の内容に沿った形で、文法事項などをできるだけ簡潔に、ほかの言語の経験がある程度ある人なら、意味がわかるようにまとめました。参照用にご利用ください。
付録Bには訳者が作成した、長くはないけれど、何らかの機能を果たすプログラムを集めました。原著の最初のほうの章には、まとまったコードがあまり書かれていないので、付録Bの例題をざっと読んでおくと、本文の内容がわかりやすくなると思います。とくに最初から「B.4 コマンド行計算機——コマンド行引数、文字列の置換と正規表現、外部コマンド」までは比較的単純な例になっているので、Goに馴染みのない方は最初に読んでおくことをおすすめします。
一部の内容(A.14.1からA.14.3)は、原著の内容を強く反映しているためウェブでは非公開とさせていただきます。あしからずご了承ください。
A.1 ほかの言語で(あまり)見られない点
- 簡潔さよりも理解しやすさや「安全」に重きが置かれている。典型的な例として次のようなものがある
- サイズの違う整数同士の四則演算や比較もできない
- 型の違う変数へ代入するには、明示的に型変換をしてから代入する必要がある(「2.1.6 明示的型変換」)
- 変数は使う前に必ずその型を宣言する。その際には変数名を先に、型の指定は後に書く。たとえば整数型の変数
a
の宣言は「var a int
」のようになる(後述の「A.3 変数と定数の宣言」参照) - 定義だけして使わない変数があるとコンパイルできない。ただし、定数は使わなくてもコンパイル時のエラーにはならない
import
して使わないライブラリ(「モジュール」)があるとコンパイルできない。なお、次のように「_
」を書いておけばtime
を使わなくてもコンパイルできるようになる。ただし、この場合init
(初期化用の関数)は起動されるので「まったく無視」というわけではない(「9.3.8init
関数」)
import ( "fmt" _ "time" )
- ループは
for
文だけ。while
やdo-while
を使うようなものもfor
で書けるようになっている(「4.3for
」) if
文やfor
文の条件などは(...)
で囲まないで書く- 「三項演算子」の
?:
はない。if
文を使って書く(複雑な式を書くことがあり、わかりにくくなりがちであるため。これに対してif
文を使えば長くはなるが誰にもわかりやすい) - 「配列(array)」と、配列によく似た「スライス(slice)」があるが、スライスのほうがよく使われる。配列の大きさは変更できないが、スライスのサイズは変更可能(要素の追加や削除が可能)
- 例外処理はない。関数から複数の値を返すことができ、返す複数の値の最後に
error
型を指定することでエラーを処理するのが一般的(「B.3.1 例外処理」の例や「8.1 エラー処理の基本」) - ポインタ変数(アドレスを値とする変数)を使うが、アドレスに対する演算はできない
- 構造体
S
のフィールドf
を参照するのにx.f
と書かれていた場合、x
は構造体S
を指すポインタ変数である場合もある(C言語ではx->f
と書かれるが、->
は使わずに「.
」を使う)。x
がポインタかどうかは自動的に判断される &s[i]
のように文字列やマップのアドレスをとることはできない。s
が配列またはスライスの場合、p := &s[i]
のようにのアドレスを取って*p = 111
のように代入することはできるが、*(s+1)
のようなアドレスの演算はできない
- 構造体
- 関数の引数は値渡し(call by value)。ただし、スライスとマップはポインタ(変数のアドレス)が渡されるので、読み出された側で代入したりすることで変更できる。ただし、スライスを延長しても、呼び出し側に戻るときには前のサイズのままになる(「6.7 マップとスライスの違い」)
defer
を使うことで、関数を抜ける際に(たとえ「パニック」になったとしても)必ず実行することを指定できる。ファイルの利用後のクローズ(「5.4defer
」)やWaitGroup
のデクリメント(「B.6.2 ウェブサイトのチェック——WaitGroup
版」や「10.5.10WaitGroup
の利用」)などがその典型
A.2 型とゼロ値
Go言語では以下に示す型(基本型と合成型、その他)が用いられます。各型には「ゼロ値(zero value)」があり、初期値を指定しないで変数を宣言するとゼロ値が初期値となります(以下の説明において、章の記載がないものは2章で説明しています)。
なお、スライス、マップ、関数、インタフェース、チャネルはポインタとして実装されています。ポインタとして実装されている型のゼロ値はすべてnil
となります(nil
は値がないことを示す型のない識別子)。
A.2.1 基本型(組み込み型)
- 論理(値)型——型名は
bool
。true
あるいはfalse
の値を取る。ゼロ値はfalse
- 数値型——ゼロ値は
0
(複素数型のゼロ値は実部も虚部の0
の複素数)- 整数型
int8
、int16
、int32
、int64
、uint8
、uint32
、uint64
——サイズごと。u
が付くものは符号なし(0と正の数のみ)byte
——unit8
の別名int
、uint
——プラットフォームによりint32
あるいはint64
のいずれかと同じサイズ。整数のデフォルトの型rune
——ひとつのコードポイントを表現。'a'
、'\n'
などのように「'
」で囲む。int32
のエイリアスだが文字を表す場合はrune
を用いるほうが意図が明確になる(文字を記憶するのが目的だが、中身は整数。したがってゼロ値は0
)uintptr
(14章)
- 浮動小数点数型——
float32
、float64
- 複素数型——
complex64
、complex128
- 整数型
- 文字列型(3章)——型名は
string
。ゼロ値は""
(空文字列)。「"あいうえお"
」のように「"
」で囲む。文字列中に「\
」、改行、「"
」を含めたい場合は「`
」(バッククオート)で囲む。Goのソースコードは常にUTF-8で書かれるので、(16進のエスケープを使わない限り)文字列リテラルはUTF-8で書かれる
ファイルから読み込んだデータは多くの場合バイト列(Go的に書くと「byte
のスライス」つまり[]byte
)として読み込まれます。たとえば次のコードで、変数b
の型は[]byte
となります。
b, err := os.ReadFile("testdata/xxx.json")
byte
はuint8
の別名なので[]uint8
でも同じですが、整数として扱っているわけではないので[]byte
としたほうが意図が明確になります。ただし、「fmt.Printf("%T", b)
」としてb
の型を出力すると「[]uint8
」と表示されてしまうので、慣れるまでは要注意です。
文字列に関する操作をするのに、[]byte
を直接操作できる場合もありますし、文字列(string
)に変換してから行う必要がある場合があります。概して、文字列に変換してから操作したほうがわかりやすいでしょうが、バイト列を直接操作したほうが効率的でしょう。
A.2.2 合成型(複合データ型、コンテナ型)、その他
- 配列(「3.1 配列」)——Goではあまり使われない(表には現れないが裏で使われている)。通常、下の「スライス」を使う
- スライス(「3.2 スライス」)——ゼロ値は
nil
。たとえばスライスを返す関数でnil
を返すとエラーが起こったことを示せる。[]int{}
は要素がないint
のスライスを意味する「空のスライス」でゼロ値ではない - マップ(「A.3.5 マップの宣言とリテラル」および「3.4 マップ」)——ほかの言語で「辞書」「連想配列」「ハッシュ」などと呼ばれているもので、ある型の値から別の型の値への対応関係の集まり。ゼロ値は
nil
。たとえばmap[string]int
は「文字列→整数」へのマップ。map[string]int{}
は「要素のないマップ」でゼロ値ではない。マップはこの「要素のないマップ」から始まることになる - セット(集合)——Goにセットはない。ただし、マップを使ってシミュレートできる(「3.4 マップ」)
- 構造体(「A.4 構造体の宣言とリテラル」および「3.5 構造体」)——「フィールド名」と「その値」のペアの0個以上の繰り返し。ゼロ値は各フィールドがゼロ値になっているもの
- 関数(「A.9 関数」および5章)——ゼロ値は
nil
- ポインタ型(6章)——ゼロ値は
nil
- インタフェース型(7章)——ゼロ値は
nil
- チャネル(10章)——ゼロ値は
nil
A.3 変数と定数の宣言
変数は「変数名」「型」の順番で宣言するのが基本ですが、初期値の指定などのためにさまざまな形式が用意されています。
A.3.1 基本型の変数の宣言
下の例はint
(整数)の例。ほかの型についても同じように宣言できます。
var x int = 10 // 型と初期値を指定する var x = 10 // 型を省略する(デフォルトの型になる。この場合int) var x int // 初期値を省略する(intの「ゼロ値」が入る。この場合0) var x, y int = 10, 20 // 多重代入 var x, y int // 初期値の省略(ゼロ値が入る) var x, y = 10, "hello" // 型の異なる変数の宣言 x := 10 // 「var x = 10」と同じ。関数内でのみ使える。一番簡単。 // 宣言リスト var ( x int y = 20 d, e = 40, "hello" // eは文字列 )
A.3.2 配列とスライスの宣言
// 配列(イミュータブル、つまり変更できない。サイズも固定)。配列やスライスの大きさは「len(x)」でわかる var x [3]int // サイズを指定する var x = [3]int{10, 20, 30} var x = [12]int{1, 5: 4, 6, 10: 100, 15} var x = [...]int{10, 20, 30} // サイズは要素数で決まる var x [2][3]int // 多次元配列をシミュレート // スライス(サイズ可変の配列) var x = []int{10, 20, 30} // サイズを指定すると配列になってしまう var x = []int{1, 5: 4, 6, 10: 100, 15} var x = []int{} // nilスライス var x [][]int // 多次元のスライスのシミュレート
A.3.3 スライスへの要素の追加
var y []int // [] y = append(y, 10) // [10] var x = []int{1, 2, 3} // [1 2 3] x = append(x, 4) // [1 2 3 4] x = append(x, 5, 6, 7) // [1 2 3 4 5 6 7] x = []int{1, 2} // [1 2] z := []int{20, 30, 40} // [20 30 40] x = append(x, z...) // [1 2 20 30 40] // 別のスライスの全要素を追加
A.3.4 サブスライス(スライスの要素の削除)
スライスの一部の要素を削除したものを作りたい場合は、「:
」を使って「サブスライス」を作ります(「3.2.6 スライスのスライス」)。サブスライスを作っても、領域は共有されているので、要素を変更すると共有しているすべての(サブ)スライスが影響を受けることに注意が必要です。
x := []int{1, 2, 3, 4} // [1 2 3 4] y := x[:2] // [1 2] z := x[1:] // [2 3 4] d := x[1:3] // [2 3] e := x[:] // [1 2 3 4]
A.3.5 マップの宣言とリテラル
詳しくは「3.4 マップ」を参照してください(XXX/exXXX.go
)。
intMap := map[string]int{} // 文字列→整数のマップで要素がないもの intMap["abc"] = 1 intMap["あいう"] = 10 fmt.Println(intMap["abc"]) // 1 fmt.Println(intMap["あいう"]) // 10 m := map[string]any{ // interface{}も可 (go 1.18でanyが導入された) // any にはどのような型の値も入れられる "文字": "あいう", "数字": 123, "マップ": map[string]int { "加奈": 32, "涼子": 23, }, } fmt.Println(m["文字"]) // あいう fmt.Println(m["数字"]) // 123 fmt.Println(m["マップ"]) // map[加奈:32 涼子:23] x := m["マップ"].(map[string]int) // 「7.10.1 型アサーション」 // m["マップ"]のままではanyなので、そのまま m["マップ"]["加奈"]とはできない // 型アサーションで、map[string]int だと(強制的に)仮定する fmt.Println(x["加奈"]) // 32 fmt.Printf("%T\n", x) // map[string]int // %Tで型を出力
A.3.6 make
を使ったスライスやマップの生成
x := make([]int, 5) // サイズ5のスライス x := make([]int, 5, 10) // サイズ5、キャパシティ(確保された容量)10のスライス ages := make(map[string]int, 10) // サイズは0になるが、要素10個分のメモリを確保。 // 文字列→整数 のマップ
A.3.7 定数の宣言
詳しくは「2.3 定数」を参照してください。
const x int64 = 10 // 型付きの定数宣言
const y int64 = x
// var z int = x // 型が違うのでコンパイル時のエラーになる
const a = 2 * 4 // 型なし(untyped)の定数宣言
var b int = a // OK。型なしの定数は代入できる(stringなどには代入できない)
var c float64 = a // OK。同様
var d byte = a // OK。同様
const ( // 宣言リスト(複数の定数をまとめて宣言)
idKey = "id"
nameKey = "name"
)
A.4 構造体の宣言とリテラル
構造体(「3.5 構造体」)を使うことで、複数のデータを併せもつ独自の型(type)を定義できます。Goにクラスはありませんが、構造体とインタフェースを併用することで、類似の(より優れた?)機能を実現できます(後述の「A.10 メソッド」も参照)。
type person struct { // person型の宣言 name string // 「フィールド名」はnameで、その型はstring age int pet string } var fred person // person型の変数fredを宣言 bob := person{} // 空の構造体リテラルを使う。全フィールドがゼロ値 kanako := person{ // 構造体リテラルを使った変数宣言 "加奈子", // name // 全フィールドの値を順番に指定する 40, // age "猫", // pet // 最後に「,」がある点に注意 } hiroe := person{ // 構造体リテラル。「フィールド名」と「値」を指定 age: 30, // 指定されていないもの(pet)はゼロ値("")になる name: "広江", } // 無名構造体 var person struct { // 変数personの型宣言 name string age int pet string } person.name = "ボブ" person.age = 50 person.pet = "dog" pet := struct { // 型を指定して name string kind string }{ // 初期値を指定 name: "ポチ", kind: "dog", }
A.5 型のエイリアス
キーワードtype
を使って、型のエイリアスを作れます。
型Foo
が定義されているとき、次のようにすることで、Foo
とBar
は同じフィールドとメソッドをもつようになります。
type Bar = Foo
A.6 iota
Goに列挙型はありませんがiota
(「7.2.7 iota
と列挙型」)を使うと、1
ずつ増加する値を一連の定数に割り当てることができます。
type ServerStatus int // サーバの状況 const ( Unknown ServerStatus = iota // 不明 値は0になるが、0を使うのは非イディオム的 Up // 動作中 値は1 Down // ダウン 値は2 )
A.7 制御構造
if
文、switch
文、for
文を使います。ループにwhile
文などはありません。goto
文もあります。詳細は4章を参照してください。
A.7.1 if
とfor
の基本形
for count:=1; i<10 ; count++ { //「条件」を指定しないと無限ループになる if num, err := readUserAnswer(count); err != nil || num < 1 || 10 < num { fmt.Println("ハズレ!") } else if num != answer { // 必ず 「} else if」を同じ行に書く fmt.Println("残念でした。ハズレです。") } else { printSuccessMessage(count) break // forループを抜ける } }
A.7.2 for-range
の例
dict := map[string][]string{ // string -> []string(文字列のスライス)のマップ "fungible": []string{"代替可能な", "代替性のある", "代替物"}, ..., } eng = "fungible" translations := dict[eng] // dictはstring->[]stringのマップ for _, t := range translations { // インデックス(添字)は無視 fmt.Printf("%s|%s\n", eng, t); }
A.7.3 switch
loop: // 下のbreakでループを抜けるための「ラベル」 for count := 1; ; count++ { // 無限ループ switch num, err := readUserAnswer(count); { case err != nil || num < 1 || 10 < num: fmt.Println("おかしな入力!") // breakがなくても、このcaseはここで終了 case num != answer:__ fmt.Println("ハズレ!") // breakがなくても、このcaseはここで終了 default: printSuccessMessage(count) // 当たったときのメッセージを表示 break loop // forループを抜ける。breakだけだとdefaultを抜けるだけになってしまう } // forの終わり
A.8 演算子
基本的にはC言語などとよく似ています(2章参照)。ただし、変数i
をインクリメント(+1)するのはi++
で、++i
はエラーになります。同様に--i
も使えません。
チャネルに関連して<-
が用いられます(10章参照)。
ch <- x
- チャネル
ch
にx
を送信 x = <- ch
- チャネル
ch
から受信した値をx
に代入
A.9 関数
比較的単純な関数の定義のパターンを示します(「B.3.2 関数とループ」に例があります。詳しくは5章参照)。
import "errors" ... func 関数名(〈引数とその型〉
) (〈戻り値の型〉
, error) { if〈エラーの条件〉
{ return〈戻り値の型のゼロ値〉
, errors.New("エラーメッセージ") } ... return〈戻り値〉
, nil }
〈引数とその型〉
には、0個以上の「引数と型」の組を「,
」で区切ってリストする。同じ型のものが続く場合は途中の型を省略できる(例:「i, j int
」)〈戻り値の型〉
には、型を「,
」で区切ってリストする- エラーが起こる可能性がある場合は、最後に
error
を置く - 戻り値がひとつだけの場合は「
(...)
」で囲む必要はない
- エラーが起こる可能性がある場合は、最後に
〈エラーの条件〉
が成立した場合、〈戻り値の型のゼロ値〉
には、戻り値ごとにそのゼロ値を指定したものを返す- 関数名の前に
(p Person)
のような、(〈変数名〉 〈型〉)
のパターンが書かれている場合は、後述の「メソッド」(特定の型にのみ働く関数)を表す - 関数名の前に
go
を付けて関数を呼ぶだけで、その関数をゴルーチンとして起動できる。したがって、呼ばれる側の定義は普通の関数と変わるところはない
A.10 メソッド
Goに「クラス」やその「継承」の機能はありませんが、型(type)に対してメソッド(型メソッド)を定義できます(「7.2 メソッド」)。「型」とその型用の「型メソッド」は同じモジュール(通常は同じファイル)に書きます。
次の例は構造体に付随するメソッドを定義するものです(ch07/ex0701.go
)。
// 型Personを定義 type Person struct { LastName string // 姓 FirstName string // 名 Age int // 年齢 } // 型Personに付随するメソッドStringを定義(PersonにメソッドStringを付加) func (p Person) String() string { // 「(p Person)」がレシーバの指定 return fmt.Sprintf("%s %s:年齢%d歳", p.LastName, p.FirstName, p.Age) }
組み込みの型(int
など)には直接メソッドは定義できませんが、基本型から別の型を作って(type MyInt int
)、その型に付随するメソッドを定義すれば、int
に対して定義したかのように使えます。
A.11 継承
上に書いたようにGoに「クラス」はありません(したがって、クラス間の継承機能もありません)が、構造体とそれに付随するメソッドを定義することで、クラスのような機能を実現できます。
さらに、「埋め込みフィールド」を利用することで、構造体に上下関係を入れ、継承と類似の機能を実現できます(「7.3 埋め込みによる合成」)。
A.12 インタフェース
インタフェースはGoで唯一の抽象型(実装を提供しない型)で、次の2つの側面をもちます(「7.5 インタフェースとは」)。
- メソッドの集合——他言語の「インタフェース」のように、特定のクラスが満たすべき要件(実装するべき一群のメソッド)を示す
- 型——変数がインタフェースを基盤とする型をもつことで、さまざまなことができる。たとえば、任意の型の値を代入できる変数を定義できる
2.の例として特によく使われるのがinterface{}
(Go 1.18からはany
とも書けるようになりました)。これは、「0個のメソッドが定義された型」ということになるので、任意の値がこの条件を満たすことになります。したがってany
と宣言された変数には、任意の型の値を記憶できます。
A.13 ゴルーチン
並行処理をするのにゴルーチン(10章)が使えます(「B.6 ゴルーチン、チャネル、WaitGroup
」にサンプルあり)。
- 関数の呼び出し時に、前に
go
が書かれていると別のゴルーチンとして呼び出される(並行に実行される) - 並行に実行されている関数(ゴルーチン)間では情報のやり取りにチャネルを使える
- チャネル型は次のいずれかの構文をもつ(チャネルでやり取りするデータの型が
string
の場合)。string
の代わりに、構造体なども指定できる
chan string
chan <-string
<- chan string
- チャネルにはバッファ付きのものとバッファがないものがあり、いずれも
make
を使って生成する。バッファ付きのものは第2引数にバッファの大きさ(キャパシティ)を指定する
ch := make(chan int, 10) // バッファのキャパシティ10のチャネルの生成
A.13.1 チャネルからの読み込みのパターン
チャネルからの読み込み時には次のようなパターンが使われます。
A.13.1.1 変数への代入
a := <-ch // チャネル変数chから値を読み込み、aに代入する
A.13.1.2 無視する
<-ch // チャネルchから読み込み、読み込んだデータは無視。ひとつ「空読み」される
A.13.1.3 select
文のcase
での代入
select { // 複数のチャネルのいずれかから読み込む case v := <-ch1: // 読み込めるチャネルが複数ある場合、どのチャネルから読み込まれるかは不定 fmt.Println("ch1:", v) case v := <-ch2: fmt.Println("ch2:", v) ... }
A.13.1.4 for-range
ループ
for v := range ch { // チャネルchがクローズされるまで、値を読み込む fmt.Println(v) // 読むものがなければ待たされる(ポーズする) }
A.14 Goの「イディオム」
本文にあげられているGoの「イディオム」や「慣習」などをまとめました。ほかにも、(逆に)「イディオム的でないもの」や、コード中でよく使われるパターンもあげました。登場順に並んでいますが、Go言語で特に重要、あるいは特徴的と思われるものは太字にしました。
「A.14.1 イディオム的なもの」から「A.14.3 その他、Go の「哲学」が感じられる選択」は、原著の内容を強く反映しているためウェブでは非公開とさせていただきます。あしからずご了承ください。
A.14.4 備忘録
その他、覚えておくと役に立つ場面がありそうな事柄や、初見では意味が即座に理解できないと思われるデータ型などをあげておきます。
fmt.Printf
などの書式指定(verb)で便利なもの(「B.2fmt
パッケージの動詞(verb)」)%T
——型に関する情報を出力%v
——デフォルトの形式で出力(システム側で「良きに計らってくれる」)%+v
——構造体のフィールド名も出力%#v
——Goのリテラル表現(2章など参照)で出力(たとえば文字列では「\t
」「\n
」などを使う)
- カンマokイディオムの利用場面
- マップで「ゼロ値と結びつけられているキー」と「マップに存在しないキー」の区別(「10.3.3 チャネルのクローズ」)
- 型アサーションでパニックを回避する(「7.10.1 型アサーション」および「12.5 コンテキストによる値の伝搬」)
- チャネルがクローズされたかどうかの検知(「10.3.3 チャネルのクローズ」)
- 返された値を正しい型に型アサートする(「12.5 コンテキストによる値の伝搬」)
- ビルドやテスト時に長いパスを表示したくない場合はフラグ
-trimpath
を指定する - 「
.
」と「_
」で始まる名前のファイルと、testdata
という名前のディレクトリはコンパイルの対象外となる(go build
でコンパイル対象にならない) - 組み込みの型(
int
など)には直接メソッドは定義できないが、(type MyInt int
のように)基本型から別の型を作って、その型に付随するメソッドを定義すれば、int
に対して定義したかのように使える - リテラル(「
12
」「1.4142
」「"あいうえお"
」など直に値を示したもの)は型がないため(untyped
)、そのリテラルと互換性をもつ任意の変数に代入できる。たとえば、-12
はint
、int8
、int16
、int32
などに代入できる。ただしサイズに注意が必要。たとえば、1000
をbyte
型の変数に代入しようとすると溢れてしまうのでエラーになる(2章) - ジェネリクスを使う場合、型パラメータで型が決まる変数
x
のゼロ値を戻したいとき、その変数をvar
を使って宣言しておけば自動的にゼロ値が入るので、それをreturn
で戻せばよい(「15.2 Go言語のジェネリクスの概要」)
以下に初心者には意味の把握が簡単ではないと思われる表現をあげておきます。
[]int{}
——int
のスライスを要素なしで初期化したもの(空のスライスリテラル)map[string]int{}
——「string
→int
」のマップを要素なしで初期化したものinterface{}
——空インタフェース。Go 1.18からはany
と書けるようになった。文字どおり「0個のメソッドを実装する型」を表す。任意の型は0個以上のメソッドを実装しているので、この型として宣言した変数には任意の型の値を記憶できることになる。外部とのやり取り(たとえばネット経由のやり取り)をするときにこの型の変数に記憶するといった用途で用いるmap[string]interface{}{}
——「string
→interface{}
」のマップを要素なしで初期化したもの(Go 1.18以降ではmap[string]any{}
とも書ける)。たとえば次のコードで変数m
は「string
→any
」のマップで要素がない変数となる
m := map[string]any{}
struct{}
——空の構造体(フィールドがひとつもない構造体)。メモリを消費しないので、単にシグナルを送るために使われる(たとえば「10.5.4 doneチャネルパターン」に例がある)struct{}{}
——上の「空の構造体」のインスタンス(フィールドとその値を0個指定している。フィールドがひとつもない構造体という型の具体的な値)。すぐ下のマップでセットを表現するときに使えるmap[int]struct{}{}
——「int
→struct{}
」のマップを要素なしで初期化したもの。マップのキーはint
で、値としては「フィールドのない構造体」しか取らない。これを使うとマップでセットを表現でき、しかもメモリ使用量が少ない(「3.4.4 マップとセット」)。ただしわかりやすくはないので、素直に「int
→bool
」(true
あるいはfalse
)のマップを用いるのがよいと考える人も多い