go-data-structures について
Go のデータ構造の選択と、スライス、マップ、値のコピーに関する実践的な指針をまとめた開発スキルです。データ表現の選択や、スライスとエイリアスのよくある問題の回避に役立ちます。
主な機能
- 01データ構造の選択指針
- 02スライスの宣言と append の扱い
- 03二次元スライスのパターン
- 04マップによる集合の表現
- 05コピーとエイリアスへの注意
活用シーン
- 01Go の機能に適したコレクションの選択
- 02JSON における nil と空スライスの処理
- 03値のコピーによるエイリアス問題の回避
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-data-structuresインストール後、Claude Code で/go-data-structuresを入力して読み込まれたか確認します。
同梱ファイルreferences/SLICES.md
詳細
Go のデータ構造
リソースの振り分け
references/SLICES.md- nil スライスと空スライスの使い分け、スライスのコピー、またはスライスの容量やエイリアスを管理する方法を判断するときに参照してください。
データ構造の選び方
What do you need?
├─ Ordered collection of items
│ ├─ Fixed size known at compile time → Array [N]T
│ └─ Dynamic size → Slice []T
│ ├─ Know approximate size? → make([]T, 0, capacity)
│ └─ Unknown size or nil-safe for JSON? → var s []T (nil)
├─ Key-value lookup
│ └─ Map map[K]V
│ ├─ Know approximate size? → make(map[K]V, capacity)
│ └─ Need a set? → map[T]struct{} (zero-size values)
└─ Need to pass to a function?
└─ Copy at the boundary if the caller might mutate it
このスキルが適用されない場合:データ構造への並行アクセス(ミューテックス、アトミック操作)については、go-concurrency を参照してください。API 境界での防御的コピーについては、go-defensive を参照してください。パフォーマンスのために容量を事前確保する方法については、go-performance を参照してください。
スライス
append 関数
結果は必ず代入してください — 基底配列が変わる可能性があります。
x := []int{1, 2, 3}
x = append(x, 4, 5, 6)
// Append a slice to a slice
x = append(x, y...) // Note the ...
二次元スライス
独立した内側のスライス(個別に拡大・縮小できます):
picture := make([][]uint8, YSize)
for i := range picture {
picture[i] = make([]uint8, XSize)
}
単一の割り当て(サイズが固定の場合に、より効率的です):
picture := make([][]uint8, YSize)
pixels := make([]uint8, XSize*YSize)
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}
空のスライスの宣言
空のリテラルより nil スライスを優先してください。
// Good: nil slice
var t []string
// Avoid: non-nil but zero-length
t := []string{}
どちらも len と cap は 0 ですが、nil スライスが推奨されるスタイルです。
JSON の場合の例外:nil スライスは null にエンコードされますが、[]string{}
は [] にエンコードされます。JSON 配列が必要な場合は、nil ではないスライスを使用してください。
インターフェースを設計する際は、nil のスライスと nil ではない長さ 0 のスライスを区別しないでください。
マップ
セットの実装
マップがセットとしてのみ使われる場合は、map[T]struct{} を使用してください。空の構造体はストレージを消費せず、メンバーシップを表す意図も明確になります。
attended := map[string]struct{}{"Ann": {}, "Joe": {}}
if _, ok := attended[person]; ok {
fmt.Println(person, "was at the meeting")
}
値が存在するかどうかとは別の意味を持つ場合に限り、ブール値をマップの値として使用してください。
コピー
別のパッケージにある構造体をコピーする際は注意してください。その型がポインター型(*T)にメソッドを持つ場合、値をコピーするとエイリアスによるバグが発生することがあります。
一般的なルール: メソッドがポインター型 *T に関連付けられている場合、型 T の値をコピーしないでください。このルールは、bytes.Buffer、sync.Mutex、sync.WaitGroup およびそれらを含む型に適用されます。
// Bad: copying a mutex
var mu sync.Mutex
mu2 := mu // almost always a bug
// Good: pass by pointer
func increment(sc *SafeCounter) {
sc.mu.Lock()
sc.count++
sc.mu.Unlock()
}
クイックリファレンス
| トピック | 要点 |
|---|---|
| スライス | append の結果は必ず代入する。[]T{} より nil スライスを優先する |
| セット | メンバーシップのみを扱うセットには map[T]struct{} を使用する |
| コピー | メソッドが *T にある場合は T をコピーしない。エイリアスに注意する |
関連スキル
- 防御的コピー:変更を防ぐため、API 境界でスライスやマップをコピーする場合は go-defensive を参照してください
- 容量のヒント:既知のワークロードに合わせてスライスやマップの容量を事前確保する場合は、go-performance を参照してください
- 反復パターン:スライス、マップ、チャネルに対して range ループを使う場合は、go-control-flow を参照してください
- 宣言スタイル:
new、make、var、複合リテラルから選ぶ場合は、go-declarations を参照してください