重複の削除、要素の存在チェック、構造体スライスのソート。Goでこのあたりを書くたびに、似たような for ループを毎回書いていましたよね。Go 1.21 で標準入りした slices と maps パッケージは、その大半を1行に畳めます。
現行の Go 1.26.4(2026年6月リリース)までに、検索・ソート・削除・重複除去・マップ操作がジェネリック関数として出揃いました。sort.Slice に渡していた比較クロージャや、自前の contains ヘルパーは、もう書かなくて済みます。
標準パッケージに寄せると何が減るのか
Go 1.18のジェネリクス導入前は、汎用のスライス操作を書こうとすると interface{} とリフレクションに頼るしかありませんでした。sort.Slice がその代表です。比較関数をインデックスで書くため、要素そのものを触れません。
// Go 1.20以前: インデックス越しの比較
sort.Slice(users, func(i, j int) bool {
return users[i].age < users[j].age
})
slices は型パラメータで要素を直接受け取ります。比較対象が users[i] ではなく a, b になり、コンパイル時に型が決まる。実行時リフレクションが消えるぶん、誤った型を渡すミスはビルドで止まります。
- 検索:
Contains/Index/BinarySearch - ソート:
Sort/SortFunc/SortStableFunc - 編集:
Delete/Insert/Compact/Replace - マップ:
maps.Clone/maps.Equal/maps.Keys
存在チェックと検索はContainsから
ContainsとIndexで自前ヘルパーを捨てる
「このスライスに値が入っているか」だけのために、自前の contains ヘルパーを書いていたコードは多い。slices.Contains がそのまま置き換えます。
package main
import (
"fmt"
"slices"
)
func main() {
langs := []string{"Go", "Python", "Rust"}
fmt.Println(slices.Contains(langs, "Go"))
fmt.Println(slices.Index(langs, "Python"))
fmt.Println(slices.Index(langs, "TypeScript"))
}
実行結果。Index は見つからなければ -1 を返します。
true
1
-1
条件で探したいときは ContainsFunc と IndexFunc。述語を渡すと、最初に true を返した要素で判定します。
nums := []int{3, 7, 12, 5}
hasEven := slices.ContainsFunc(nums, func(n int) bool {
return n%2 == 0
})
fmt.Println(hasEven) // 12が該当
true
ソート済みならBinarySearch
BinarySearch は昇順ソート済みのスライスが前提。インデックスと、見つかったかの真偽値を返します。見つからない場合は「挿入すべき位置」が返るので、ソート順を保ったまま追加できます。
sorted := []int{2, 4, 6, 8, 10}
i, found := slices.BinarySearch(sorted, 6)
fmt.Println(i, found)
i, found = slices.BinarySearch(sorted, 7)
fmt.Println(i, found) // 7は未登録、挿入位置は3
2 true
3 false
ソートはSortとSortFuncで書き分ける
比較関数はcmp.Compareに寄せる
数値や文字列など cmp.Ordered を満たす型は slices.Sort だけで昇順に並びます。構造体や独自順序は SortFunc に比較関数を渡す。比較関数は a<b で負、a>b で正、等しければ 0 を返す約束です。標準の cmp.Compare に委譲すると自前の三項分岐が要りません。
package main
import (
"cmp"
"fmt"
"slices"
)
type user struct {
name string
age int
}
func main() {
users := []user{
{"Sato", 32},
{"Ito", 28},
{"Kato", 41},
}
slices.SortFunc(users, func(a, b user) int {
return cmp.Compare(a.age, b.age)
})
fmt.Println(users)
}
[{Ito 28} {Sato 32} {Kato 41}]
安定ソートが要るとき
同値の要素の元の並びを保ちたいなら SortStableFunc。たとえば「年齢で並べるが、同じ年齢は登録順のまま」という要件で効きます。3つの使い分けを整理します。
| 関数 | 比較 | 安定性 | 使う場面 |
|---|---|---|---|
Sort | 型の自然順序 | 不要(同値は区別なし) | int・stringの単純な昇順 |
SortFunc | 渡した関数 | 保証なし | 構造体・独自順序 |
SortStableFunc | 渡した関数 | 保証あり | 同値の元順序を残したい |
削除と重複除去でハマる2つの罠
このパッケージで一番事故りやすいのが Delete と Compact です。どちらも「元のスライスを書き換えて、結果を返り値で渡す」設計。返り値を無視すると壊れます。以前、削除した要素数だけ末尾にゼロ値が残ったスライスをそのままレスポンスに詰めてしまい、テストの期待値とズレて初めて気づいたことがあります。
slices.Deleteは返り値を必ず受ける
アンチパターンから。返り値を捨てると、元のスライス変数は長さが変わらないまま、末尾がゼロ化されます。
s := []int{10, 20, 30, 40, 50}
slices.Delete(s, 1, 3) // 返り値を捨てている
fmt.Println(s)
[10 40 50 0 0]
末尾の 0 0 は、Go 1.22以降の Delete が解放対象のスロットをゼロ化する仕様によるもの。ポインタを含む要素のメモリリークを防ぐ動きですが、返り値を受けないと長さ5のままゼロが見えてしまいます。正しくは返り値を受け直します。
s := []int{10, 20, 30, 40, 50}
s = slices.Delete(s, 1, 3) // インデックス1,2を削除
fmt.Println(s)
[10 40 50]
Compactは「連続」しか消さない
Compact の名前から「重複を全部消す」と読むと外します。消すのは 連続して並んだ 等値だけ。Unixの uniq と同じ挙動です。
s := []int{1, 1, 2, 2, 2, 3, 1}
s = slices.Compact(s)
fmt.Println(s) // 末尾の1は離れているので残る
[1 2 3 1]
全体から重複を消したいなら、先に Sort で同値を隣り合わせてから Compact に渡す。この2段が定番です。
s := []int{1, 1, 2, 2, 2, 3, 1}
slices.Sort(s)
s = slices.Compact(s)
fmt.Println(s)
[1 2 3]
mapsパッケージはCloneとEqualが実用的
標準の maps は Go 1.21時点では Clone / Copy / Equal / EqualFunc / DeleteFunc の5つから始まりました。手書きの for k, v := range コピーや、2マップの一致判定ループを置き換えます。
a := map[string]int{"x": 1, "y": 2}
b := map[string]int{"y": 2, "x": 1}
fmt.Println(maps.Equal(a, b)) // キー順は無関係
true
Cloneは浅いコピー
maps.Clone は新しいマップを返しますが、値のコピーは通常の代入と同じ。値がスライスやマップだと、中身の参照は共有されます。トップレベルのキー追加は独立する一方、ネストした要素を書き換えると元にも波及する点に気をつけてください。
original := map[string][]int{"x": {1, 2}}
cloned := maps.Clone(original)
cloned["x"][0] = 99 // 中のスライスは共有されている
fmt.Println(original["x"])
[99 2]
Keys/Valuesはiter.Seqを返す
マップのキー一覧を取る maps.Keys は Go 1.23 で追加され、スライスではなく iter.Seq を返します。golang.org/x/exp/maps 時代は []K を返していたため、移行時は戻り値が iter.Seq に変わった箇所でコンパイルが通らなくなります。受け側は次のセクションの slices 側関数とつなぎます。
slices.SortedとCollectでイテレータをつなぐ
Go 1.23で slices と maps が得たのは、iter.Seq を介した連結です。マップのキーをソート済みスライスにする処理は、これまで3行のループでした。
// Go 1.22以前
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
slices.Sorted は iter.Seq を受け取り、収集してソートまで済ませます。maps.Keys の戻り値をそのまま渡せます。
m := map[string]int{"go": 3, "py": 1, "rs": 2}
keys := slices.Sorted(maps.Keys(m))
fmt.Println(keys)
vals := slices.Sorted(maps.Values(m))
fmt.Println(vals)
[go py rs]
[1 2 3]
イテレータ自体の自作や iter.Pull の使い方は別記事のGoのイテレータを自作するで扱っています。ここで押さえたいのは、slices/maps がその仕組みの受け皿として整備された点です。
まとめ
自前で書いていたスライス・マップ操作は、slices/maps でほぼ標準関数に寄せられます。移行で押さえる勘所を整理します。
Contains/Indexで自前の存在チェックヘルパーは不要。Indexの未検出は-1- ソートは
SortFunc+cmp.Compare。同値の順序を残すならSortStableFunc Delete/Compactは返り値を必ず受け直す。Compactが消すのは連続した重複だけmaps.Cloneは浅いコピー。ネストしたスライス・マップは参照を共有する- Go 1.23の
maps.Keys/Valuesはiter.Seq。slices.Sortedと直結できる
Go 1.26環境では、sort.Slice や golang.org/x/exp/slices は標準パッケージで置き換えられます。

