Goクイズアドベントカレンダー25日目

December 25, 2020

この記事は、Goクイズ Advent Calendar 2020の25日目の記事です。まずはこちらの記事を読んでください。

問題

以下のコードをThe go2go Playgroundで実行するとどうなるか?なお、型パラメタの仕様はType Parameters - Draft Designに従う。

type Setter interface { Set(int) }

type Settable struct { n int }
func (p *Settable) Set(n int) { p.n = n }

func FromInt[T Setter](n int) T {
	var v T
	v.Set(n)
	return v
}

func main() {
	fmt.Println(FromInt[*Settable](100))
}
  1. コンパイルエラー
  2. {0}と表示される
  3. {100}と表示される
  4. パニックが発生

解答と解説

解答

答えは「4. パニックが発生」でした!

FromInt関数の型引数を*Settableとしているため、FromInt関数内の型パラメタTは、*Settable型になります。そのため、変数vのゼロ値はnilとなり、Setメソッド内のp.n = nでパニックが発生します。

以下のように、ゼロ値がnilにならないよう型引数にSettableを指定すれば良さそうですが、そうするとSettableSetメソッドを実装していないため、コンパイルエラーとなります。

func main() {
	fmt.Println(FromInt[Settable](100))
}

この問題を解決するためには、以下のように任意の型を表す型パラメタとそのポインタを表す型パラメタを定義すると良いでしょう。Setterインタフェースを実装する型はポインタであることを指定するため、型リストtype *Bを追加しています。Setterインタフェースもジェネリックな型として定義し、任意の型のポインタであることを表現しています。

FromInt関数では、T any, PT Setter[T]のように型パラメタを設けることにより、型パラメタTは任意の型、型パラメタPTはそのポインタを表しています。そうすることにより、nilではないゼロ値を得られる上に、そのポインタを取得することにより、Setメソッドを呼び出せます。

type Setter[B any] interface {
	Set(int)
	type *B
}

func FromInt[T any, PT Setter[T]](n int) T {
	var v T
	PT(&v).Set(n)
	return v
}

インスタンス化する場合は、型推論が働くため、以下のように型パラメタTに対応するSettable型だけ指定すれば良いでしょう。

func main() {
	fmt.Println(FromInt[Settable](100))
}

このように型引数を指定することにより、型パラメタPTは以下のように推論されます。

  • { T, PT }
  • { T -> Settable, PT -> *T }
  • { T -> Settable, PT -> *Settable }

複雑な型パラメタは少し難しいですが、新しい機能はワクワクしますね!それでは良いお年を。