
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
完全スライス式(Full slice expressions)
配列、配列へのポインタ、またはスライス a(文字列は除く)に対して、一次式
a[low : high : max]
は、単純スライス式 a[low : high] と同じ型、同じ長さ、同じ要素を持つスライスを構築する。さらに、結果のスライスの容量を max - low に設定することで容量を制御する。省略できるのは最初のインデックスのみであり、デフォルトは0である。配列 a をスライスした後
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
スライス t の型は []int、長さは2、容量は4、要素は以下のようになる
t[0] == 2
t[1] == 3
単純スライス式と同様に、a が配列へのポインタの場合、a[low : high : max] は (*a)[low : high : max] の省略形である。スライスされるオペランドが配列の場合、それはアドレス指定可能でなければならない。
インデックスは 0 <= low <= high <= max <= cap(a) であれば範囲内であり、そうでなければ範囲外である。定数インデックスは非負で、int 型の値で表現可能でなければならない。配列の場合、定数インデックスは範囲内でもなければならない。複数のインデックスが定数である場合、存在する定数はそれぞれの相対的な順序において範囲内でなければならない。実行時にインデックスが範囲外である場合、実行時パニックが発生する。
解説
完全スライス式ってなに?
前回学んだ a[low:high] に、3つ目のインデックス max を加えたものです。この max で、結果のスライスの容量(cap)を制限できます。
a := [5]int{1, 2, 3, 4, 5}
// 単純スライス式
s := a[1:3] // len=2, cap=4(元の配列の末尾まで)
// 完全スライス式
t := a[1:3:5] // len=2, cap=4(max-low = 5-1 = 4)
t2 := a[1:3:3] // len=2, cap=2(max-low = 3-1 = 2)← 容量を制限!
3つのインデックスの意味を整理しましょう。
| インデックス | 意味 |
|---|---|
low | 開始位置 |
high | 終了位置(ここは含まない) |
max | 容量の上限位置(ここは含まない) |
結果のスライスは以下のようになります。
- 長さ =
high - low - 容量 =
max - low
なぜ容量を制限したいの?
前回、スライスは基底配列を共有するという話をしましたね。容量を制限しないと、スライスを後から伸ばしたときに意図せず元の配列を上書きしてしまうことがあります。
a := [5]int{1, 2, 3, 4, 5}
// 容量制限なし
s := a[1:3] // len=2, cap=4
s = append(s, 99) // cap に余裕があるので、a[3] を上書きしてしまう!
fmt.Println(a) // [1, 2, 3, 99, 5] ← 元の配列が変わった!
これは意図しないバグの原因になります。完全スライス式で容量を制限すると、この問題を防げます。
a := [5]int{1, 2, 3, 4, 5}
// 容量を制限
s := a[1:3:3] // len=2, cap=2 ← 容量ぴったり
s = append(s, 99) // cap に余裕がないので、新しい配列が割り当てられる
fmt.Println(a) // [1, 2, 3, 4, 5] ← 元の配列は変わらない!
容量をぴったりに制限しておくと、append したときに新しい配列が確保されるため、元のデータに影響を与えません。
メモリのイメージ
視覚的に見てみましょう。
a := [5]int{1, 2, 3, 4, 5}
a[1:3](容量制限なし)の場合:
a: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ]
↑ ↑ ↑
low=1 high=3 cap の限界(= 5)
スライス: [ 2 ][ 3 ] len=2
[ 2 ][ 3 ][ 4 ][ 5 ] cap=4(ここまで伸ばせる)
a[1:3:3](容量を制限)の場合:
a: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ]
↑ ↑
low=1 high=3, max=3
スライス: [ 2 ][ 3 ] len=2, cap=2
max を high と同じにすることで、「窓を伸ばす余地」をなくしています。
文字列には使えない
完全スライス式は文字列に対しては使えません。単純スライス式の str[low:high] のみ使えます。
s := "hello"
sub := s[1:3] // OK! "el"
sub := s[1:3:4] // コンパイルエラー! 文字列には使えない
文字列は不変なので「容量を制限して安全に append する」という使い方が成り立たず、3番目のインデックスを指定する意味がないからです。
省略できるのは low だけ
単純スライス式では low も high も省略できましたが、完全スライス式で省略できるのは low のみです。high と max は省略できません。
a := [5]int{1, 2, 3, 4, 5}
a[:3:5] // OK! low を省略(0がデフォルト)
a[1::5] // コンパイルエラー! high は省略できない
a[1:3:] // コンパイルエラー! max は省略できない
実践での使いどころ
関数の引数としてスライスを渡すとき、呼び出し先が勝手に append して元のデータを壊さないようにする防御的プログラミングとして活用できます。
func process(data []int) {
// data に append しても呼び出し元には影響しない
data = append(data, 999)
// ...
}
a := []int{1, 2, 3, 4, 5}
process(a[1:3:3]) // 容量を制限して渡す → 安全!
fmt.Println(a) // [1, 2, 3, 4, 5] ← 元のデータはそのまま
完全スライス式を使う場面はそれほど多くありませんが、「スライスの共有による意図しない副作用を防ぎたいとき」に覚えておくと非常に役立ちます。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント