スライスは配列そのものではなく、{ 裏の配列へのポインタ, len, cap } の小さな構造体(スライスヘッダ)。
make([]int, 0, 5) は「int 5個ぶんの裏の配列をゼロ値で確保し、len=0の窓を返す」。マスの中身は既に0が入っているが、lenのカーテンで見えない。だから s[0] は panic(見える要素が1個も無い)。
→ 最終サイズが分かっているなら make([]T, 0, n) でcapを先に確保しておくのがGoの慣習。
make([]T, 0, n) | var s []T | |
|---|---|---|
| 正体 | 非nilの空(土地持ち) | nilスライス(土地なし) |
| len | 0 | 0 |
json.Marshal | [] | null |
lenはどちらも0で、普段のappendやrangeはほぼ同じ挙動。差が出るのはJSON化のとき。
第2引数(len)の使い分け:
make([]T, 0, n)(len=0スタート)io.Read の受け皿・copy の宛先 → make([]T, n)(len=nスタート)混ぜると事故る:
make([]T, n) にappend → ゼロ値n個の後ろに継ぎ足されて長さ2nになるmake([]T, 0, n) に s[i] = → panic(lenの外への添字アクセス)Read に渡す → 1バイトも読まれない(Readはlenぶんしか書かない)nilスライスをそのまま json.Marshal すると null になり、クライアントが .length を参照して死ぬ、といった事故の元。API レスポンス用のスライスは非nilで初期化しておく。
業務で読んだディスパッチャ実装の
results := make([]types.JobDispatchResult, 0, len(tenants))
は、この2つを1行で両取りしている。
cap=len(tenants) を事前確保して再確保ゼロnull ではなく [] になる防御