## 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 値)]]