Goのmake関数:スライス初期化の2つのパターンと使い分け

はじめに

Goでスライスを扱っていると、make の使い方が微妙に違う書き方を見かけることがあります。どちらも正しく動くのに、なぜ書き分けるのか。最初はあまり気にしていませんでしたが、ある程度コードを書いていくと、この違いが設計の意図を反映していることに気づきました。


make関数の基本

Goの make は、スライス・マップ・チャネルを初期化するための組み込み関数です。スライスに対しては次のように使います。

make([]T, length, capacity)
  • length(len):スライスの現在の要素数。インデックスでアクセスできる範囲。
  • capacity(cap):内部配列として確保されたメモリの大きさ。lenを超えた追加に備えるバッファ。

capを省略した場合、cap = len になります。言い換えると、lenは「今使っている領域」、capは「確保されている最大容量」です。この2つを意識するだけで、スライスの挙動がかなり読めるようになります。


パターン①:lenを指定する

s := make([]int, 5)
// len=5, cap=5
// s = [0, 0, 0, 0, 0]

特徴

make([]int, 5) は、長さ5のスライスを作ります。各要素はゼロ値で初期化されます。s[0] から s[4] まで最初からアクセス可能な状態です。

使いどころ

要素数がはっきり決まっていて、インデックスで直接代入していく場合に適しています。

results := make([]int, len(input))
for i, v := range input {
    results[i] = v * 2
}

注意点:appendとの関係

ここで初心者がよくやるミスがあります。make([]int, 5) で作ったスライスに対して append を使うと、既存の5要素の後ろに追加されます。

s := make([]int, 5)
s = append(s, 1)
// s = [0, 0, 0, 0, 0, 1]  ← 意図と違う結果になることがある

len > 0 のスライスはすでに要素が存在している状態なので、append は既存要素を置き換えません。インデックス代入と append を混在させると、予期しない結果になりやすいので注意が必要です。

「appendしたはずなのに先頭が0のまま」という混乱はこれが原因です。append はlenの末尾に追加する操作であり、既存要素を上書きする手段ではありません。このパターンを使うなら、インデックス代入に統一するのが無難です。


パターン②:len=0 + capを指定する

s := make([]int, 0, 5)
// len=0, cap=5
// s = []  ← 空

特徴

長さゼロ、つまり要素が何もない状態で始まります。ただし内部では5要素分のメモリがすでに確保されています。s[0] にアクセスしようとするとパニックになります。

使いどころ

append で要素を積み上げていく場合に適しています。

filtered := make([]int, 0, len(input))
for _, v := range input {
    if v > 0 {
        filtered = append(filtered, v)
    }
}

メリット:メモリ再確保の回避

append はスライスのlenがcapを超えると、新しい内部配列を確保してコピーが走ります。事前に cap を指定しておくことで、その再確保を抑えられます。要素数がある程度予測できる場合、このパターンはパフォーマンス上の利点があります。


2つの違いの整理

make([]T, n) make([]T, 0, n)
初期状態 n個のゼロ値要素あり 要素なし(空)
アクセス インデックスで即アクセス可 appendで追加してから
appendの挙動 既存要素の後ろに追加 先頭から追加される
主な用途 インデックス代入 appendによる逐次追加

使い分けの指針

実務では次のように考えると迷いにくいです。判断の軸はシンプルで、要素数が固定かどうかです。

インデックスで書き込むなら make([]T, n)
要素数が確定していて、results[i] = ... のように位置を指定して代入するケース。変換処理やマッピング処理に多いパターンです。入力と出力の長さが1対1で対応する処理が典型です。

appendで積み上げるなら make([]T, 0, n)
フィルタリングや条件付き収集のように、最終的な要素数が実行前には確定しないケース。要素数が可変なときはこちらを選ぶと自然です。ただし上限の見当がつくなら cap を渡しておくと効率がよいです。

どちらでも動く場面はありますが、意図を明確にするためにも使い分けを意識するとよいでしょう。


まとめ

make の第2引数と第3引数は、スライスの「現在の状態」と「将来の余裕」を表しています。len > 0 は要素がすでに存在する状態であり、len = 0 は空の器として始まる状態です。この違いを理解していると、append が絡んだバグを未然に防げます。

スライスの初期化は些細に見えますが、どう使うかを先に決めてから初期化するという順序を意識すると、コードの意図が伝わりやすくなります。

コメント

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