## What — スライスの正体は3点セット スライスは配列そのものではなく、`{ 裏の配列へのポインタ, len, cap }` の小さな構造体(スライスヘッダ)。 - **len** = いま「見えている」要素数(カーテンの開いている幅) - **cap** = 裏の配列に確保済みのマス数(土地の広さ) `make([]int, 0, 5)` は「int 5個ぶんの裏の配列をゼロ値で確保し、len=0の窓を返す」。マスの中身は既に0が入っているが、lenのカーテンで見えない。だから `s[0]` は panic(見える要素が1個も無い)。 ### appendの実態 - **cap内**: 裏の配列の次のマスに書いてlenを+1するだけ。再確保ゼロで速い - **cap超え**: その瞬間だけ、より大きい新配列を確保して全要素コピー(おおむね倍々成長) → 最終サイズが分かっているなら `make([]T, 0, n)` でcapを先に確保しておくのがGoの慣習。 ### 「空」には2種類ある | | `make([]T, 0, n)` | `var s []T` | |---|---|---| | 正体 | 非nilの空(土地持ち) | nilスライス(土地なし) | | len | 0 | 0 | | `json.Marshal` | `[]` | `null` | lenはどちらも0で、普段のappendやrangeはほぼ同じ挙動。差が出るのはJSON化のとき。 ## How — 使い分けと防御 第2引数(len)の使い分け: - **appendで育てる** → `make([]T, 0, n)`(len=0スタート) - **添字代入・`io.Read` の受け皿・`copy` の宛先** → `make([]T, n)`(len=nスタート) 混ぜると事故る: > [!warning] 混ぜたときの事故パターン > - `make([]T, n)` にappend → ゼロ値n個の**後ろに**継ぎ足されて長さ2nになる > - `make([]T, 0, n)` に `s[i] =` → panic(lenの外への添字アクセス) > - len=0のスライスを `Read` に渡す → 1バイトも読まれない(Readはlenぶんしか書かない) nilスライスをそのまま `json.Marshal` すると `null` になり、クライアントが `.length` を参照して死ぬ、といった事故の元。API レスポンス用のスライスは非nilで初期化しておく。 業務で読んだディスパッチャ実装の ```go results := make([]types.JobDispatchResult, 0, len(tenants)) ``` は、この2つを1行で両取りしている。 1. サイズ既知のappend用途 → `cap=len(tenants)` を事前確保して再確保ゼロ 2. tenantsが0件でもレスポンスが `null` ではなく `[]` になる防御 ## 関連 - [[Go]] - [[Goメソッドレシーバー(ポインタ vs 値)]]