
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
select文(Select statements)
select 文は、可能な送信操作または受信操作の集合のうち、どれが進行するかを選択する。switch 文に似ているが、ケースがすべて通信操作を参照する点が異なる。
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr = Expression .
RecvStmt を持つケースは、RecvExpr の結果を1つまたは2つの変数に代入することができ、短縮変数宣言を使用して宣言することもできる。RecvExpr は(括弧で囲まれていてもよい)受信操作でなければならない。default ケースは最大1つであり、ケースリストのどこにでも配置できる。
select 文の実行はいくつかのステップで進行する:
- 文内のすべてのケースについて、受信操作のチャネルオペランドと、送信文のチャネルおよび右辺の式は、
select文に入った時点でソース順に正確に一度だけ評価される。結果は受信元または送信先のチャネルの集合と、対応する送信値である。この評価における副作用は、どの通信操作が進行するよう選択されるか(もしあれば)に関わらず発生する。短縮変数宣言または代入を持つ RecvStmt の左辺の式はまだ評価されない。 - 通信の1つ以上が進行可能な場合、進行可能な1つが一様な擬似ランダム選択によって選ばれる。そうでなければ、default ケースがあればそのケースが選ばれる。default ケースがなければ、
select文は少なくとも1つの通信が進行可能になるまでブロックする。 - 選択されたケースが default ケースでない限り、対応する通信操作が実行される。
- 選択されたケースが短縮変数宣言または代入を持つ RecvStmt である場合、左辺の式が評価され、受信した値(または複数の値)が代入される。
- 選択されたケースの文リストが実行される。
nil チャネルでの通信は決して進行できないため、nil チャネルのみを持ち default ケースを持たない select は永遠にブロックする。
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // i3, ok := <-c3 と同じ
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
case a[f()] = <-c4:
// 以下と同じ:
// case t := <-c4
// a[f()] = t
default:
print("no communication\n")
}
for { // ランダムなビット列を c に送信する
select {
case c <- 0: // 注意: 文なし、fallthrough なし、ケースの統合なし
case c <- 1:
}
}
select {} // 永遠にブロック
解説
select文ってなに?
select 文は、複数のチャネル操作のうち、準備ができたものを1つ選んで実行する仕組みです。チャネル専用の switch と考えるとわかりやすいです。
select {
case msg := <-ch1:
fmt.Println("ch1 から受信:", msg)
case msg := <-ch2:
fmt.Println("ch2 から受信:", msg)
case ch3 <- value:
fmt.Println("ch3 に送信")
}
「どのチャネルが先に準備できるかわからないけど、準備できたものから処理したい」という場面で使います。
実行の流れを理解する
select の動作は5ステップですが、実用上覚えておくべきことは3つです。
1. すべてのチャネル式が先に評価される
select {
case <-getChan(): // getChan() はここで呼ばれる
case sendChan() <- getValue(): // sendChan() と getValue() もここで呼ばれる
}
// どのケースが選ばれるかに関係なく、全部評価される
2. 準備できたケースからランダムに1つ選ばれる
ch := make(chan int, 1)
ch <- 42
select {
case v := <-ch:
fmt.Println("受信:", v)
case ch <- 100:
fmt.Println("送信")
}
// バッファが1なので、受信も送信もできる → どちらが選ばれるかはランダム
複数のケースが同時に進行可能な場合、特定のケースが優先されることはありません。公平にランダムに選ばれます。
3. どのケースも進行できなければブロックする
select {
case v := <-ch1: // ch1 に値がない
fmt.Println(v)
case v := <-ch2: // ch2 にも値がない
fmt.Println(v)
}
// どちらかに値が届くまでブロック
default ケース:ノンブロッキング操作
default をつけると、どのチャネルも準備できていないときに即座に default が実行されます。つまりブロックしません。
select {
case msg := <-ch:
fmt.Println("受信:", msg)
default:
fmt.Println("まだ何も届いていない")
}
これを使って、ノンブロッキングな送受信を実現できます。
// ノンブロッキング送信:バッファが満杯なら諦める
select {
case ch <- value:
fmt.Println("送信成功")
default:
fmt.Println("バッファが満杯、スキップ")
}
よくある使い方
1. タイムアウト
select {
case result := <-ch:
fmt.Println("結果:", result)
case <-time.After(5 * time.Second):
fmt.Println("タイムアウト")
}
2. キャンセル(context との組み合わせ)
select {
case result := <-ch:
fmt.Println("結果:", result)
case <-ctx.Done():
fmt.Println("キャンセルされた:", ctx.Err())
}
3. 複数のソースからの受信を多重化する
for {
select {
case msg := <-emailCh:
handleEmail(msg)
case msg := <-smsCh:
handleSMS(msg)
case <-quit:
return
}
}
4. ランダムな値の生成
原文の例にある「ランダムなビット列」のパターンです。
for {
select {
case c <- 0:
case c <- 1:
}
}
両方のケースが常に進行可能(受信者がいれば)なので、ランダムに 0 か 1 が選ばれます。
nil チャネルの活用
nil チャネルでの通信は永遠にブロックするので、select のケースを動的に無効化するテクニックに使えます。
var ch1, ch2 <-chan int = activeCh1, activeCh2
for {
select {
case v, ok := <-ch1:
if !ok {
ch1 = nil // ch1 を nil にすると、このケースは二度と選ばれない
continue
}
process(v)
case v, ok := <-ch2:
if !ok {
ch2 = nil
continue
}
process(v)
}
if ch1 == nil && ch2 == nil {
break // 両方閉じたら終了
}
}
select {} で永遠にブロック
空の select はケースが1つもないので、永遠にブロックします。
select {} // プログラムをここで止める
メインのゴルーチンを終了させずに、他のゴルーチンを走り続けさせたいときに使うことがあります。ただし、通常は sync.WaitGroup やチャネルで待機するほうが意図が明確です。
switch との違い
見た目は似ていますが、重要な違いがあります。
| switch | select | |
|---|---|---|
| 対象 | 値や型 | チャネル操作 |
| 評価順序 | 上から下(最初の一致) | ランダム(進行可能なものから) |
| fallthrough | 使える(式switchのみ) | 使えない |
| ブロック | しない | default がなければする |
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント