
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
close(Close)
チャネル ch に対して、組み込み関数 close(ch) は、そのチャネルでこれ以上値が送信されないことを記録する。ch が受信専用チャネルである場合はエラーとなる。閉じたチャネルへの送信または閉じたチャネルの再度の close は実行時パニックを引き起こす。nil チャネルの close もまた実行時パニックを引き起こす。close を呼び出した後、以前に送信されたすべての値が受信された後は、受信操作はチャネルの型のゼロ値をブロックせずに返す。複数値の受信操作は、受信した値とともにチャネルが閉じられているかどうかの指示を返す。
close の引数の型が型パラメータの場合、その型集合のすべての型はチャネルでなければならない。それらのチャネルのいずれかが受信専用チャネルである場合はエラーとなる。
解説
close ってなに?
close は、チャネルに「もう値を送りません」と宣言する組み込み関数です。
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
ch <- 3
close(ch) // 送信完了を通知
}()
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// close されると range ループが自動的に終了する
チャネルの状態と操作の関係
チャネルの各状態で何が起きるかを整理しておきましょう。
| 操作 | 正常なチャネル | 閉じたチャネル | nil チャネル |
|---|---|---|---|
送信 ch <- v | 正常動作 | パニック | 永遠にブロック |
受信 <-ch | 正常動作 | ゼロ値を即座に返す | 永遠にブロック |
| close | 正常動作 | パニック | パニック |
閉じたチャネルに対する操作でパニックするのは送信と再close だけです。受信は安全に行えます。
閉じたチャネルからの受信
close された後のチャネルは、送信済みの値をすべて返し終えると、ゼロ値をブロックせずに返し続けます。
ch := make(chan int, 3)
ch <- 10
ch <- 20
close(ch)
fmt.Println(<-ch) // 10(送信済みの値)
fmt.Println(<-ch) // 20(送信済みの値)
fmt.Println(<-ch) // 0(ゼロ値。チャネルは閉じている)
fmt.Println(<-ch) // 0(何度読んでもゼロ値)
ゼロ値と本物の値を区別する
チャネルから 0 が返ってきたとき、「本当に 0 が送信されたのか」「チャネルが閉じたのか」を区別するには、2値受け取りを使います。
v, ok := <-ch
if ok {
fmt.Println("受信:", v) // 本物の値
} else {
fmt.Println("チャネルは閉じている") // ゼロ値
}
close のルール
1. 送信側が閉じる
これは Go の最も重要な慣例です。受信側が閉じると、送信側がパニックする危険があります。
// 正しいパターン
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 送信側が閉じる
}
2. 受信専用チャネルは閉じられない
func f(ch <-chan int) {
close(ch) // コンパイルエラー! 受信専用チャネルは close できない
}
これは「送信側が閉じる」という原則を型レベルで強制する仕組みです。
3. 二重 close はパニック
ch := make(chan int)
close(ch)
close(ch) // 実行時パニック!
close を呼ぶのは一度だけ。複数のゴルーチンが同じチャネルを閉じようとしないよう設計する必要があります。
close が不要な場合もある
すべてのチャネルを閉じる必要はありません。close が必要なのは、受信側に「もう値は来ない」と伝えたいときだけです。
// close が必要:range で受け取るため
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch) // range に終了を伝える
}()
for v := range ch { ... }
// close が不要:個別に受信するだけ
ch := make(chan int)
go func() { ch <- 42 }()
v := <-ch // 1回受信して終わり。close は不要
チャネルは GC(ガベージコレクション)で回収されるので、ファイルのように「必ず閉じなければリソースがリークする」ということはありません。
よくあるパターン:完了通知
close を「処理が完了した」というシグナルとして使うパターンです。
done := make(chan struct{})
go func() {
heavyWork()
close(done) // 完了を通知
}()
<-done // 完了を待つ
close すると、<-done でブロックしているすべてのゴルーチンが同時に解放されます。1つのチャネルに値を送信する方法では一度に1つのゴルーチンしか起こせませんが、close なら何人でも一斉に起こせます。これがブロードキャスト的な通知パターンです。
done := make(chan struct{})
// 複数のワーカーが done を待っている
for i := 0; i < 10; i++ {
go func(id int) {
<-done // 全員がここでブロック
fmt.Printf("worker %d: 開始\n", id)
}(i)
}
close(done) // 全ワーカーが一斉に動き出す
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント