Go言語入門:言語仕様 -Vol.131-

スポンサーリンク
Go言語入門:言語仕様 -Vol.131- 用語解説
Go言語入門:言語仕様 -Vol.131-
この記事は約8分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

スライス、map、チャネルの作成(Making slices, maps and channels)

組み込み関数 make は、スライス、map、またはチャネル型、あるいは型パラメータでなければならない型 T を取り、任意で型固有の式のリストが続く。型 T の値(*T ではない)を返す。メモリは初期値の節で説明されているとおりに初期化される。

呼び出し           型 T             結果

make(T, n)       スライス          長さ n、容量 n の型 T のスライス
make(T, n, m)    スライス          長さ n、容量 m の型 T のスライス

make(T)          map               型 T の map
make(T, n)       map               約 n 要素の初期空間を持つ型 T の map

make(T)          チャネル           型 T のバッファなしチャネル
make(T, n)       チャネル           型 T のバッファ付きチャネル、バッファサイズ n

make(T, n)       型パラメータ       以下を参照
make(T, n, m)    型パラメータ       以下を参照

最初の引数が型パラメータの場合、その型集合のすべての型は同じ基底型を持たなければならず、それはスライスまたはmap型でなければならない。または、チャネル型がある場合はチャネル型のみでなければならず、すべて同じ要素型を持ち、チャネルの方向が矛盾してはならない。

サイズ引数 nm はそれぞれ整数型であるか、整数型のみを含む型集合を持つか、または型なし定数でなければならない。定数のサイズ引数は非負で、int 型の値で表現可能でなければならない。型なし定数の場合は int 型が与えられる。nm の両方が提供され定数である場合、nm より大きくてはならない。スライスとチャネルでは、実行時に n が負であるか m より大きい場合、実行時パニックが発生する。

s := make([]int, 10, 100)       // len(s) == 10, cap(s) == 100 のスライス
s := make([]int, 1e3)           // len(s) == cap(s) == 1000 のスライス
s := make([]int, 1<<63)         // 不正: len(s) が int 型の値で表現できない
s := make([]int, 10, 0)         // 不正: len(s) > cap(s)
c := make(chan int, 10)         // バッファサイズ 10 のチャネル
m := make(map[string]int, 100)  // 約 100 要素の初期空間を持つ map

map型とサイズヒント nmake を呼び出すと、n 個のmap要素を保持する初期空間を持つmapが作成される。正確な動作は実装依存である。


解説

make ってなに?

make は、スライス、map、チャネルを初期化して使える状態にする組み込み関数です。この3つの型はゼロ値が nil なので、使う前に make で初期化する必要があります。

s := make([]int, 5)           // 使えるスライス
m := make(map[string]int)     // 使えるmap
ch := make(chan int)           // 使えるチャネル

make と new の違い

この2つは初心者が混同しやすいポイントです。

// make:スライス、map、チャネル専用。初期化された「値」を返す
s := make([]int, 5)    // []int 型の値

// new:任意の型に使える。ゼロ値へのポインタを返す
p := new(int)          // *int 型のポインタ
makenew
返す型T*T
対象スライス、map、チャネルのみ任意の型
初期化内部構造を初期化するゼロ値をメモリに確保するだけ

スライス、map、チャネルは内部に複雑な構造(ポインタ、長さ、容量など)を持つので、new でゼロ値のポインタを作っただけでは使えません。make は内部構造まで正しく初期化してくれます。

スライスの make

// 長さと容量が同じ
s := make([]int, 5)
// len=5, cap=5, 全要素がゼロ値(0)

// 長さと容量を別々に指定
s := make([]int, 5, 10)
// len=5, cap=10, 全要素がゼロ値(0)

容量を大きめに取っておくと、append で要素を追加するときに基底配列の再確保が発生しにくくなります。

// 悪い例:要素数が事前にわかっているのに容量を指定しない
var s []int
for i := 0; i < 10000; i++ {
    s = append(s, i)  // 何度も基底配列が再確保される → 遅い
}

// 良い例:事前に容量を確保
s := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
    s = append(s, i)  // 基底配列の再確保が起きない → 速い
}

map の make

// 基本
m := make(map[string]int)

// サイズヒントつき
m := make(map[string]int, 100)

サイズヒントはあくまで「ヒント」であり、mapが自動的に成長するのを止めるわけではありません。100要素を超えても問題なく追加できます。ただし、事前にサイズを指定しておくと、内部のハッシュテーブルの再構築回数が減るので、パフォーマンスが向上します。

// map リテラルでも初期化できる
m := map[string]int{"a": 1, "b": 2}

// 空の map リテラル ≠ nil
m := map[string]int{}  // 空だが nil ではない。make(map[string]int) と同等

チャネルの make

// バッファなしチャネル
ch := make(chan int)
// 送信側と受信側が同時に準備できないと通信できない

// バッファ付きチャネル
ch := make(chan int, 10)
// バッファに10個まで溜められる

バッファサイズは用途によって使い分けます。

// バッファなし:同期的な通信(送受信のタイミングを合わせたい)
ch := make(chan int)

// バッファ付き:非同期的な通信(生産者と消費者の速度差を吸収したい)
ch := make(chan int, 100)

サイズ引数の制約

make([]int, 10, 100)   // ✅ len=10, cap=100
make([]int, 10, 0)     // コンパイルエラー! len > cap
make([]int, -1)        // 実行時パニック! 負のサイズ
make([]int, 1<<63)     // コンパイルエラー! int に収まらない

定数の場合はコンパイル時にチェックされ、変数の場合は実行時にチェックされます。

make を使わないとどうなる?

nil のスライス、map、チャネルはある程度は使えますが、制限があります。

// nil スライス:append は使えるが、インデックスアクセスはパニック
var s []int
s = append(s, 1)   // OK
// s[0] = 1         // パニック(len が 0 なので)

// nil map:読み取りはゼロ値を返すが、書き込みはパニック
var m map[string]int
v := m["key"]      // OK(0 が返る)
// m["key"] = 1     // パニック!

// nil チャネル:送受信が永遠にブロック
var ch chan int
// ch <- 1          // 永遠にブロック
// <-ch             // 永遠にブロック

map とチャネルは make で初期化するのが必須と考えてよいです。スライスは nil のまま append で成長させるパターンも一般的ですが、要素数がわかっているなら make で容量を確保するほうが効率的です。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました