TL;DR
- Go 1.20 から、事前宣言された
comparable制約はanyを含むすべての比較可能な型で満たされるようになった。 - その代わり、
comparableを使うジェネリックなコードが実行時に panic しうるようになり、静的な型安全性の一部を実行時のチェックと引き換えにした。
内容
問題
Go 1.20 より前は、map[any]string はコンパイルできるのに、同じことをジェネリクスで書いた genericLookupTable[any, string] はコンパイルできなかった。この制限が提案されていた maps.Clone のようなジェネリックなライブラリの妨げになっていた。
型パラメータと制約
Go 1.18 で型パラメータが入り、制約は「型引数として許される型の集合」を表すものになった。インターフェースは型の集合として理解され、A|B のような union や ~T の近似も書ける。型パラメータの値に使える演算は、型集合のすべての型がその演算を持つときだけ許される。
比較可能な型
== は型の範囲が開いているので、制約の中に列挙できない。そこで事前宣言の comparable があるが、その型集合はインターフェース自身を含まないので、any は入らない。記事によると、インターフェースの値どうしの比較は実行時に panic しうるので、comparable には次の型だけが入る。
comparablecontains only types that the compiler guarantees will not panic with==.
こうした型を「strictly comparable」と呼ぶ。
インターフェースの実装と制約の充足を分ける
Go 1.20 は変数への代入に使う「インターフェースの実装」と、型引数を渡すときの「制約の充足」を分けた。前者は変わらない。後者には例外ができ、interface{ comparable; E } の形の制約は、E を実装する比較可能な型 T で満たされる。
a type that supports
==also satisfiescomparable(even though it may not implement it).
ただし、型パラメータにはこの例外がない。any で制約された型パラメータは、今も comparable を満たさない。
影響
この変更は後方互換だが、静的な型安全性の一部を失った。
We have given up static type safety for a run-time check.
コンパイル時に strictly comparable であることを確かめたいときは、型を型引数ではなく制約として渡せば例外がかからない。Go 1.18 では一時期この挙動に近い実装があり、その後仕様に合わせて変わったので、作者は「一周して戻ってきた」と締めくくっている。
キーワード
- Go
- ジェネリクス
- 型パラメータ
- 型集合
- comparable
- strictly comparable