Q. Go言語である型を満たす型パラメータとその型自体が違う型と言われる
型パラメータを持つ型と具体的な型は区別される。
Q. 型が一致しているはずなのにエラーが出る
実際のコードは遥かに複雑で意味のあるものでしたが、非常に単純化すると次のようなコードについて質問を受けました。
package main
type Container[T Printer] struct{
Printer T
}
type Printer interface{
Print()
}
func Run[T Printer](c Container[T]) {
PrintContainer(c)
}
func PrintContainer(c Container[Printer]) {}
func main() {}
これを実行しようとすると、PrintContainer()
を呼び出している箇所で次のエラーが出ます。
cannot use c (variable of struct type Container[T]) as Container[Printer] value in argument to PrintContainer
「エラーでContainer[T]
とContainer[Printer]
が違う型であると言われるが、T
はGenericsで指定されている通り当然Printer
インターフェースを実装しているのだから、Container[T]
とContainer[Printer]
は同様の型であり通るのが自然ではないか」といった内容の質問を受けました。
A. Goの型は厳しい
Goの型システムでは、同じインターフェースを実装していたとしても異なる型として厳密に区別されるということです。
まずGenericsを省いて考えましょう。
次のコードは、例えSecondaryPrinterContainerとPrinterContainerが同じインターフェースを備えていてもエラーを吐きます。
func Run(c SecondaryPrinterContainer) {
PrintContainer(c)
}
func PrintContainer(c PrinterContainer) {}
仕様書には、次のように型定義は個別の型を作成するとあります。これは例え同じインターフェースを備えていようが別の型として認識されることを示していると思います。
A type definition creates a new, distinct type with the same underlying type and operations as the given type and binds an identifier, the type name, to it.
https://go.dev/ref/spec#Type_definitions
続いて、Genericsについて考えます。
仕様書には、Genericsは型パラメータを型引数に置き換えることによってインスタンス化され、非Genericsな名前付き型が生成されるとあります。つまり、型パラメータを持つ型と具体的な型は区別されます。
A generic function or type is instantiated by substituting type arguments for the type parameters … Instantiating a type results in a new non-generic named type; instantiating a function produces a new non-generic function.
https://go.dev/ref/spec#Instantiations
よって、元のコードの対応方法として、次のような方法が考えられます。
1. 型パラメータを一致させる
型パラメータを一致させる、つまりPrintContainerも同様のGenericsを使うようにすれば対応できます。
func Run[T Printer](c Container[T]) {
PrintContainer(c)
}
func PrintContainer[T Printer](c Container[T]) {}
2. 型アサーション
ライブラリなどの都合で型パラメータを一致させることが出来ない場合は、Genericsを諦めるか次のようにアサーションをするしかないように思います。
func Run[T Printer](c Container[T]) {
var printerContainer Container[Printer]
printerContainer.Printer = c.Printer
PrintContainer(printerContainer)
}
func PrintContainer(c Container[Printer]) {}