
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
送信文(Send statements)
送信文はチャネルに値を送信する。チャネル式はチャネル型でなければならず、チャネルの方向は送信操作を許可しなければならず、送信される値の型はチャネルの要素型に代入可能でなければならない。
SendStmt = Channel "<-" Expression .
Channel = Expression .
チャネル式と値の式は、通信が開始される前にどちらも評価される。通信は送信が進行可能になるまでブロックする。バッファなしチャネルへの送信は、受信者が準備できている場合に進行できる。バッファ付きチャネルへの送信は、バッファに空きがある場合に進行できる。閉じたチャネルへの送信は実行時パニックを引き起こして進行する。nil チャネルへの送信は永遠にブロックする。
ch <- 3 // チャネル ch に値 3 を送信する
チャネル式の型が型パラメータである場合、その型集合のすべての型は送信操作を許可するチャネル型でなければならず、すべて同じ要素型を持たなければならず、送信される値の型はその要素型に代入可能でなければならない。
解説
送信文の基本
ch <- 値 の形で、チャネルに値を送り込みます。受信演算子 <-ch の対になる操作ですね。
ch := make(chan int)
go func() {
ch <- 42 // チャネルに 42 を送信
}()
v := <-ch // 42 を受信
fmt.Println(v) // 42
ブロックの挙動
送信文は、送信が完了するまで**ブロック(待機)**します。いつ進行できるかは、チャネルの種類によって異なります。
バッファなしチャネル:受信者がいないと進めない
ch := make(chan int) // バッファなし
ch <- 42 // 誰かが <-ch するまで、ここで止まる
バッファなしチャネルは、送信側と受信側が同時に準備できて初めて通信が成立します。電話に例えると、相手が出るまで待たされるイメージです。
バッファ付きチャネル:バッファに空きがあれば進める
ch := make(chan int, 2) // バッファサイズ 2
ch <- 1 // バッファに空き → すぐに進む
ch <- 2 // バッファに空き → すぐに進む
ch <- 3 // バッファが満杯 → 誰かが受信するまでブロック
バッファ付きチャネルは、郵便受けに例えるとわかりやすいです。郵便受けに空きがあれば手紙を入れてすぐ帰れますが、いっぱいだと誰かが取り出すまで待たないといけません。
危険なケース:閉じたチャネルと nil チャネル
この2つの挙動は受信操作とは異なるので、しっかり区別しましょう。
| 操作 | 閉じたチャネル | nil チャネル |
|---|---|---|
送信 ch <- v | パニック | 永遠にブロック |
受信 <-ch | ゼロ値が即座に返る | 永遠にブロック |
// 閉じたチャネルへの送信 → パニック!
ch := make(chan int)
close(ch)
ch <- 1 // 実行時パニック!
// nil チャネルへの送信 → デッドロック
var ch chan int // nil
ch <- 1 // 永遠にブロック
閉じたチャネルへの送信がパニックになるのは、「もう通信は終わり」と宣言したチャネルに新たなデータを送るのは矛盾しているからです。これは送信側の責任で防ぐべきエラーです。
送信側がチャネルを閉じるのが原則
Go の慣例として、チャネルを閉じるのは送信側です。受信側が閉じると、送信側がパニックする危険があります。
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 送信が終わったら送信側が閉じる
}
func consumer(ch <-chan int) {
for v := range ch {
fmt.Println(v)
}
// range は close を検出して自動的にループを終了する
}
評価順序
原文にあるとおり、チャネル式と値の式は通信が始まる前にどちらも評価されます。
ch <- expensiveCalc()
// ステップ1:ch が評価される(どのチャネルに送るか)
// ステップ2:expensiveCalc() が評価される(何を送るか)
// ステップ3:通信開始(ブロックする可能性あり)
つまり、expensiveCalc() の計算は送信がブロックされる前に完了しています。
チャネルの方向制限
送信文を使うには、チャネルが送信を許可する方向でなければなりません。
func send(ch chan<- int) {
ch <- 42 // ✅ 送信専用チャネル → 送信OK
}
func receive(ch <-chan int) {
ch <- 42 // ❌ コンパイルエラー! 受信専用チャネルには送信できない
}
関数の引数で chan<-(送信専用)や <-chan(受信専用)を使うことで、関数が意図しない操作をすることをコンパイル時に防げます。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント