
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
長さと容量(Length and capacity)
組み込み関数 len と cap はさまざまな型の引数を取り、int 型の結果を返す。実装は結果が常に int に収まることを保証する。
呼び出し 引数の型 結果
len(s) 文字列型 バイト単位の文字列長
[n]T, *[n]T 配列の長さ(== n)
[]T スライスの長さ
map[K]T mapの長さ(定義されたキーの数)
chan T チャネルバッファにキューされた要素数
型パラメータ 以下を参照
cap(s) [n]T, *[n]T 配列の長さ(== n)
[]T スライスの容量
chan T チャネルバッファの容量
型パラメータ 以下を参照
引数の型が型パラメータ P の場合、呼び出し len(e)(または cap(e))は P の型集合の各型に対して有効でなければならない。結果は、P がインスタンス化された型引数に対応する型を持つ引数の長さ(または容量)である。
スライスの容量は、基底配列に空間が割り当てられている要素数である。常に以下の関係が成り立つ:
0 <= len(s) <= cap(s)
nil のスライス、map、またはチャネルの長さは0である。nil のスライスまたはチャネルの容量は0である。
s が文字列定数の場合、式 len(s) は定数である。s の型が配列または配列へのポインタであり、式 s がチャネル受信や(非定数の)関数呼び出しを含まない場合、式 len(s) と cap(s) は定数である。この場合、s は評価されない。それ以外の場合、len と cap の呼び出しは定数ではなく、s は評価される。
const (
c1 = imag(2i) // imag(2i) = 2.0 は定数
c2 = len([10]float64{2}) // [10]float64{2} は関数呼び出しを含まない
c3 = len([10]float64{c1}) // [10]float64{c1} は関数呼び出しを含まない
c4 = len([10]float64{imag(2i)}) // imag(2i) は定数であり、関数呼び出しは発行されない
c5 = len([10]float64{imag(z)}) // 不正: imag(z) は(非定数の)関数呼び出し
)
var z complex128
解説
len と cap の基本
len は「今いくつ入っているか」、cap は「最大いくつ入れられるか」を返します。
s := make([]int, 3, 10)
fmt.Println(len(s)) // 3(現在の要素数)
fmt.Println(cap(s)) // 10(基底配列のサイズ)
型ごとの使い方
// 文字列:バイト数(文字数ではない!)
len("hello") // 5
len("こんにちは") // 15(1文字3バイト × 5文字)
// 配列:常に固定
a := [5]int{1, 2, 3}
len(a) // 5
cap(a) // 5(配列では len == cap)
// スライス
s := make([]int, 3, 10)
len(s) // 3
cap(s) // 10
// map:キーの数
m := map[string]int{"a": 1, "b": 2}
len(m) // 2
// cap(m) // コンパイルエラー! map に cap は使えない
// チャネル
ch := make(chan int, 5)
ch <- 1
ch <- 2
len(ch) // 2(バッファに入っている要素数)
cap(ch) // 5(バッファの容量)
mapに cap が使えないのは、mapのサイズは動的に伸縮するため「最大容量」という概念がないからです。
nil のときは 0
nil のスライス、map、チャネルに対して len や cap を呼んでも、パニックしません。0 が返ります。
var s []int
var m map[string]int
var ch chan int
fmt.Println(len(s)) // 0
fmt.Println(cap(s)) // 0
fmt.Println(len(m)) // 0
fmt.Println(len(ch)) // 0
nil チェックをせずにそのまま len を呼べるので、コードがシンプルになります。
// nil チェック不要
func isEmpty(s []int) bool {
return len(s) == 0 // s が nil でも正しく動く
}
0 <= len(s) <= cap(s) の関係
スライスでは常にこの不等式が成り立ちます。
s := make([]int, 3, 10)
// len=3, cap=10
s = s[:5] // len=5, cap=10(len を cap の範囲内で伸ばせる)
s = s[:10] // len=10, cap=10(cap まで伸ばせる)
// s = s[:11] // パニック! cap を超えている
len と cap が定数になる場合
配列の len と cap はコンパイル時に決まる定数です。この性質を使って、定数式の中で len を使えます。
const size = len([10]int{}) // 10(コンパイル時定数)
これが以前学んだ range の節で「len(testdata.a) が定数なのでレンジ式が評価されない」というケースにつながります。
var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a は評価されない(nil でもパニックしない)
// len(testdata.a) は定数 7
f(i) // i は 0 から 6
}
[7]int の長さは常に 7 なので、コンパイラは testdata.a を実際に評価せずに長さを知ることができます。
文字列の len は「バイト数」
これは繰り返しになりますが、非常に重要なので改めて強調します。
s := "Go言語"
fmt.Println(len(s)) // 8(バイト数)
fmt.Println(len([]rune(s))) // 4(文字数)
文字数を知りたい場合は utf8.RuneCountInString を使うのが最も効率的です。
import "unicode/utf8"
n := utf8.RuneCountInString("Go言語") // 4
[]rune に変換する方法だと新しいスライスが確保されるため、文字数を数えるだけなら RuneCountInString のほうがメモリ効率がよいです。
よくある使い方
// スライスが空かどうか
if len(s) == 0 { ... }
// map にキーがあるか(len で判定するのではなく、ok パターンを使う)
if v, ok := m[key]; ok { ... }
// チャネルにデータがあるか
if len(ch) > 0 { ... } // ただし並行処理では信頼できない
// スライスに append の余地があるか
if len(s) < cap(s) {
// append しても新しい配列は確保されない
}
チャネルの len は並行処理の文脈では注意が必要です。len(ch) > 0 をチェックした直後に別のゴルーチンが値を受信してしまう可能性があるため、この値に依存した制御は避けるべきです。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント