{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/go-comparable/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__6345677ea35ab30001cac68d","title":"Goで構造体をcomparableとして使う場合の注意点","slug":"go-comparable","featured":false,"feature_image":null,"excerpt":"Go1.18から導入された型パラメータ comparable は、 == や != で比較可能な型のみを受け入れます。\n\nmapのキーにジェネリックな型を指定するような関数でよく使うと思います。\n\nfunc ToMap[T comparable](ss []T) map[T]struct{} {\n\tm := make(map[T]struct{}, len(ss))\n\tfor _, s := range ss {\n\t\tm[s] = struct{}{}\n\t}\n\treturn m\n}\n\nint や string などの組み込み型はもちろん、構造体も comparable を満たします。\n\ntype User struct {\n\tID   int\n\tName string\n}\n\n\nfunc main() {\n\tus := []User{\n\t\t{\n\t\t\tID:   1,\n\t\t\tName: \"1\",\n\t\t},\n\t\t{\n\t\t\tID:   2,\n\t\t\tName: \"2\",\n\t\t},\n\t}\n\tm := ToMap(us)\n\t_, ok := m[User{ID: 1, Name: \"1\"}]\n\tfm","custom_excerpt":null,"visibility":"public","created_at_pretty":"11 October, 2022","published_at_pretty":"02 January, 2023","updated_at_pretty":"02 January, 2023","created_at":"2022-10-11T21:54:22.000+09:00","published_at":"2023-01-02T10:39:00.000+09:00","updated_at":"2023-01-02T10:40:56.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Yu Takahashi","slug":"yu","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Yu Takahashi","slug":"yu","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null},"primary_tag":{"name":"Go","slug":"go","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"},"tags":[{"name":"Go","slug":"go","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"}],"plaintext":"Go1.18から導入された型パラメータ comparable は、 == や != で比較可能な型のみを受け入れます。\n\nmapのキーにジェネリックな型を指定するような関数でよく使うと思います。\n\nfunc ToMap[T comparable](ss []T) map[T]struct{} {\n\tm := make(map[T]struct{}, len(ss))\n\tfor _, s := range ss {\n\t\tm[s] = struct{}{}\n\t}\n\treturn m\n}\n\nint や string などの組み込み型はもちろん、構造体も comparable を満たします。\n\ntype User struct {\n\tID   int\n\tName string\n}\n\n\nfunc main() {\n\tus := []User{\n\t\t{\n\t\t\tID:   1,\n\t\t\tName: \"1\",\n\t\t},\n\t\t{\n\t\t\tID:   2,\n\t\t\tName: \"2\",\n\t\t},\n\t}\n\tm := ToMap(us)\n\t_, ok := m[User{ID: 1, Name: \"1\"}]\n\tfmt.Println(ok) // true\n}\n\n\nただし、構造体を使う場合、実体とポインタの判定の違いを理解しておかないとバグを生む可能性があります。\n\nfunc main() {\n\t{\n\t\tus := []User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tm := ToMap(us)\n        \n\t\t_, ok := m[User{ID: 1, Name: \"1\"}]\n\t\tfmt.Println(ok) // true\n        \n\t\tfmt.Println(us[0] == User{ID: 1, Name: \"1\"}) // true\n\t}\n\t{\n\t\tus := []*User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tm := ToMap(us)\n        \n\t\t_, ok := m[&User{ID: 1, Name: \"1\"}]\n\t\tfmt.Println(ok) // false\n        \n\t\tfmt.Println(us[0] == &User{ID: 1, Name: \"1\"}) // false\n        \n\t\t_, ok = m[us[0]]\n\t\tfmt.Println(ok) // true\n\t}\n}\n\n\n構造体の実体はフィールドが全て一致しているかどうかで判定され、ポインタはアドレスが一致しているかどうかで判定されます。\n\nそのため、以下のように、スライスの要素から重複を削除して返す関数を作った場合、ポインタを要素に持つスライスを渡すと期待通りの結果にならなくなります。\n\nfunc ToUnique[T comparable](ss []T) []T {\n\tm := make(map[T]struct{}, len(ss))\n\tunique := make([]T, 0, len(ss))\n\tfor _, s := range ss {\n\t\tif _, ok := m[s]; !ok {\n\t\t\tunique = append(unique, s)\n\t\t\tm[s] = struct{}{}\n\t\t}\n\t}\n\treturn unique\n}\n\nfunc main() {\n\t{\n\t\tus := []User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tfmt.Println(ToUnique(us)) // [{1 1} {2 2}]\n\t}\n\t{\n\t\tus := []*User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tfmt.Println(ToUnique(us)) // [0xc000010078 0xc000010090 0xc0000100a8 0xc0000100c0]\n\t}\n}\n\n\n構造体を == で比較したりmapのキーにしたりする機会が殆どないので忘れてました。","html":"<p>Go1.18から導入された型パラメータ <code>comparable</code> は、 <code>==</code> や <code>!=</code> で比較可能な型のみを受け入れます。</p><p>mapのキーにジェネリックな型を指定するような関数でよく使うと思います。</p><pre><code>func ToMap[T comparable](ss []T) map[T]struct{} {\n\tm := make(map[T]struct{}, len(ss))\n\tfor _, s := range ss {\n\t\tm[s] = struct{}{}\n\t}\n\treturn m\n}</code></pre><p><code>int</code> や <code>string</code> などの組み込み型はもちろん、構造体も <code>comparable</code> を満たします。</p><pre><code>type User struct {\n\tID   int\n\tName string\n}\n\n\nfunc main() {\n\tus := []User{\n\t\t{\n\t\t\tID:   1,\n\t\t\tName: \"1\",\n\t\t},\n\t\t{\n\t\t\tID:   2,\n\t\t\tName: \"2\",\n\t\t},\n\t}\n\tm := ToMap(us)\n\t_, ok := m[User{ID: 1, Name: \"1\"}]\n\tfmt.Println(ok) // true\n}\n</code></pre><p>ただし、構造体を使う場合、実体とポインタの判定の違いを理解しておかないとバグを生む可能性があります。</p><pre><code>func main() {\n\t{\n\t\tus := []User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tm := ToMap(us)\n        \n\t\t_, ok := m[User{ID: 1, Name: \"1\"}]\n\t\tfmt.Println(ok) // true\n        \n\t\tfmt.Println(us[0] == User{ID: 1, Name: \"1\"}) // true\n\t}\n\t{\n\t\tus := []*User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tm := ToMap(us)\n        \n\t\t_, ok := m[&amp;User{ID: 1, Name: \"1\"}]\n\t\tfmt.Println(ok) // false\n        \n\t\tfmt.Println(us[0] == &amp;User{ID: 1, Name: \"1\"}) // false\n        \n\t\t_, ok = m[us[0]]\n\t\tfmt.Println(ok) // true\n\t}\n}\n</code></pre><p>構造体の実体はフィールドが全て一致しているかどうかで判定され、ポインタはアドレスが一致しているかどうかで判定されます。</p><p>そのため、以下のように、スライスの要素から重複を削除して返す関数を作った場合、ポインタを要素に持つスライスを渡すと期待通りの結果にならなくなります。</p><pre><code>func ToUnique[T comparable](ss []T) []T {\n\tm := make(map[T]struct{}, len(ss))\n\tunique := make([]T, 0, len(ss))\n\tfor _, s := range ss {\n\t\tif _, ok := m[s]; !ok {\n\t\t\tunique = append(unique, s)\n\t\t\tm[s] = struct{}{}\n\t\t}\n\t}\n\treturn unique\n}\n\nfunc main() {\n\t{\n\t\tus := []User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tfmt.Println(ToUnique(us)) // [{1 1} {2 2}]\n\t}\n\t{\n\t\tus := []*User{\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   1,\n\t\t\t\tName: \"1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   2,\n\t\t\t\tName: \"2\",\n\t\t\t},\n\t\t}\n\t\tfmt.Println(ToUnique(us)) // [0xc000010078 0xc000010090 0xc0000100a8 0xc0000100c0]\n\t}\n}\n</code></pre><p>構造体を <code>==</code> で比較したりmapのキーにしたりする機会が殆どないので忘れてました。</p>","url":"https://ghost.tech.anti-pattern.co.jp/go-comparable/","canonical_url":null,"uuid":"3b4b481c-a049-4257-aea1-ec43ab734e5b","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"6345677ea35ab30001cac68d","reading_time":1}},"pageContext":{"slug":"go-comparable"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}