O hirunewani blog

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

Created at

型パラメータを持つ型と具体的な型は区別される。

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