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]) {}