imu-note
いむたろ
いむたろ
@imutaroh
新卒エンジニア / データ基盤 × AI

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スライス(土地なし)
len00
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スタート)

混ぜると事故る:

混ぜたときの事故パターン
  • 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で初期化しておく。

業務で読んだディスパッチャ実装の

results := make([]types.JobDispatchResult, 0, len(tenants))

は、この2つを1行で両取りしている。

  1. サイズ既知のappend用途 → cap=len(tenants) を事前確保して再確保ゼロ
  2. tenantsが0件でもレスポンスが null ではなく [] になる防御

関連