683 文字
3 分

Q. Go言語である型を満たす型パラメータとその型自体が違う型と言われる

2025-03-11

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]) {}
Q. Go言語である型を満たす型パラメータとその型自体が違う型と言われる
https://blog.ohirunewani.com/posts/go-generics-type-parameters-and-interfaces/
作者
hrdtbs
公開日
2025-03-11
ライセンス
CC BY-NC-SA 4.0