失敗したリファクタリングついにレガシーなGoサービスをジェネリクスに対応させる時が来ました。まずは、スライスに特定の値が含まれているかを確認するユーティリティ関数から小さく始めることにします。ロジックは単純なので、次のように記述しました。
func Contains[T any](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
go buildを実行し、成功メッセージを期待します。しかし、コンパイラは次のような苛立たしいエラーで進行をブロックします。
./main.go:10:10: invalid operation: v == target (type T does not satisfy comparable)
なぜGoはビルドをブロックするのかanyキーワードはinterface{}のエイリアスです。anyが「すべての操作をサポートしている」という意味だと誤解されることがよくあります。実際には、Goのコンパイラは非常に保守的です。型が比較をサポートしていることを保証できない限り、等価演算子(==)の使用を許可しません。
Goのすべての型が比較可能(comparable)なわけではありません。スライス、マップ、関数がその主な例です。もし上記のような関数に「スライスのスライス」を渡した場合、v == targetという式は実行時パニックを引き起こします。これを防ぐため、GoはTに対する要件を明示することを求めています。
クイックフィックス:「comparable」制約最も素早い解決策は、anyを組み込みのcomparable制約に置き換えることです。この制約は、Tを==および!=で動作する型に限定します。これには、ブール値、数値、文字列、ポインタ、チャネルが含まれます。また、すべてのフィールドが比較可能であれば構造体も含まれます。
修正後のコード```
// [T any] を [T comparable] に入れ替える func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { return true } } return false }
これでコンパイラは正常に動作します。`[]int`を渡せば機能します。もし`[][]string`を渡そうとすれば、本番環境でアプリがクラッシュするのを待つまでもなく、ビルド時にコンパイラが間違いを指摘してくれます。
## 比較不可能な型の処理複雑なデータを比較する必要がある場合はどうすればよいでしょうか?構造体にスライスやマップが含まれている場合、`comparable`制約は機能しません。このような場合は、Go 1.21の`slices`パッケージで確立されたパターンに従い、比較関数を渡すようにします。
### 例:比較関数を使用する```
func ContainsFunc[T any](slice []T, target T, equals func(T, T) bool) bool {
for _, v := range slice {
if equals(v, target) {
return true
}
}
return false
}
// 比較不可能な構造体での使用例
type User struct {
ID int
Tags []string // スライスが含まれるため、この構造体は比較不可能
}
users := []User{{ID: 1, Tags: []string{"admin"}}, {ID: 2}}
target := User{ID: 1}
found := ContainsFunc(users, target, func(a, b User) bool {
return a.ID == b.ID
})
検証とテスト修正が堅牢であることを確認するために、次の手順を実行してください。
- シグネチャの確認: ジェネリックパラメータが
[T comparable]になっているか確認します。- スイートの実行:go test ./...を実行します。コンパイルが通れば、型が制約を満たしていることになります。- 境界値のテスト: マップのスライス([]map[string]string)を関数に渡してみてください。明確なコンパイルエラーが表示されるはずです。これにより、制約が正しく機能していることが確認できます。## まとめ「type T does not satisfy comparable」エラーは、Goが型安全性の保証を求めているに過ぎません。文字列や整数のような単純な型にはcomparableを使用してください。スライスやマップのような複雑な型の場合は、等価演算子を避け、代わりにカスタム比較関数を使用しましょう。

