🐈

[Go Quiz] 単一の型のみによる型制約を持つ型パラメータを型に持つ変数にその型の変数を代入できるか

2023/08/17に公開

この記事の目的は、次のGoのコードがコンパイルできるか?という問題をGoの言語仕様書に基づいて解説することです。

package main

func main() {}

func F[T int]() T {
	x := 1
	var t T
	t = x
	return t
}

「自分の抱いた疑問への答えが仕様書のどこに書いてあるか」をすぐ探せる人はGoユーザーの多数派ではないと思いますが、その参考例になればと思って書きました。

https://twitter.com/shino_nobishii/status/1691806583594930568?s=20

この記事は「言語仕様書のどこを読むとその結論になるか」の説明に限り、「なぜそうなるべきなのか」というようなことはこの記事には書きません。

自分でクイズの答えを考えたい人もいると思うので、結論は記事の最後に書きます。

以下、Go1.21の言語仕様書(Version of Aug 2, 2023)に基づいて解説します。

https://go.dev/ref/spec

クイズの内容(再掲)

次のコードはコンパイルできるでしょうか?

package main

func main() {}

func F[T int]() T {
	x := 1
	var t T
	t = x
	return t
}

このコードはつぎのPlaygroundで動かせます:
https://go.dev/play/p/Qe8Bsbgk6EU

クイズのポイント - 代入可能性(Assignability)

考えやすくするためにコメントをつけてみます。

package main

func main() {} // 何もしないmain関数

// intという型制約(type constraing)を持つ型パラメータTを持つ関数Fを定義する。戻り値の型はT
// 型制約intはinterface { int } と同じ意味で、intによってのみ満たされる型制約を意味する。
func F[T int]() T { 
	// xを短縮変数宣言(short variable declaration)する。
	x := 1 // 右辺はuntyped constantの1なのでそのdefault typeであるintがxの型となる。
	var t T // T型の変数を宣言する。
	t = x // 変数tに変数xを代入する文(assingment statement)
	return t // tをreturnする文(return statement)
}

このコードでコンパイルエラーになりそうな場所はt = xの代入文くらいしかありません。

代入文が合法であるかどうかは、主に代入可能性(Assignability)というセクションに書いてあります。このクイズの答えもここにあります。

https://go.dev/ref/spec#Assignability

まずセクション全体を引用してみます。

Assigabilityセクションの引用(原文通り)

A value x of type V is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

  • V and T are identical.
  • V and T have identical underlying types but are not type parameters and at least one of V or T is not a named type.
  • V and T are channel types with identical element types, V is a bidirectional channel, and at least one of V or T is not a named type.
  • T is an interface type, but not a type parameter, and x implements T.
  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type, but not a type parameter.
  • x is an untyped constant representable by a value of type T.

Additionally, if x's type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:

  • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T's type set.
  • V is not a named type, T is a type parameter, and x is assignable to each type in T's type set.
  • V is a type parameter and T is not a named type, and values of each type in V's type set are assignable to T.

読み方

このセクションでは、型Vの値xが型Tの変数に代入できるための条件を書いています。

箇条書きのいずれかひとつに当てはまるならば、その代入が合法になります。いわゆるORの条件として読むということです。

なので、「この代入文は正しくない」ということを確かめるには、9つあるパターンを全部読んでどれにも当てはまらないことを確認する必要があります。もちろん、読み慣れてくれば明らかに当てはまらないものは読み飛ばせるようになります。

条件を確認していく

仕様のVはこのクイズの場合はintであり、Tは型パラメータのTに読み替えて読んでいくことになります。そこでVintに置き換えたものを書き下し、それを確認していきます。

  • int and T are identical.
    • 「intとTが同一の型である」は成り立ちません。
  • int and T have identical underlying types but are not type parameters and at least one of int or T is not a named type.
    • Tが型パラメータなので成り立ちません。
  • int and T are channel types with identical element types, int is a bidirectional channel, and at least one of int or T is not a named type.
    • チャネル型ではないのであてはまりません。
  • T is an interface type, but not a type parameter, and x implements T.
    • Tはインタフェース型ではないのであてはまりません。
  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type, but not a type parameter.
    • xは事前宣言された識別子nilではないのであてはまりません。
  • x is an untyped constant representable by a value of type T.
    • xは定数ではなく当てはまりません
  • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T's type set.
    • xは事前宣言された識別子nilではないのであてはまりません。
  • int is not a named type, T is a type parameter, and x is assignable to each type in T's type set.
    • intはnamed typeなのであてはまりません。残りの2条件は当てはまります。
  • int is a type parameter and T is not a named type, and values of each type in int's type set are assignable to T.
    • intは型パラメータではないのであてはまりません。

よって全部あてはまりません。

このうち、

  • int is not a named type, T is a type parameter, and x is assignable to each type in T's type set.

が特に説明を要するので、次にこれを説明します。

named type

Go1.18(ジェネリクスが追加されたバージョン)から、仕様用語としてのnamed typeが追加されました。

https://go.dev/ref/spec#Types

Predeclared types, defined types, and type parameters are called named types.

これによると、

  • 事前宣言された型
  • defined types
  • 型パラメータ

はまとめてnamed typeと呼ばれます。

これをみてから改めて代入可能性の文を読み直します。

V is not a named type, T is a type parameter, and x is assignable to each type in T's type set.

つまり、

  • Vがnamed typeではない
  • Tが型パラメータである
  • xがTの型セットのそれぞれの型に代入できる

の3つを満たすときにはxがT型の変数に代入できます。

今のクイズではVがintであり、intはdefined typeなのでnamed typeでもあります。よって1つ目の条件を満たしません。

これで冒頭のクイズを解くことができました。

類題で理解を確認する

それではnamed typeではないケースはどうなるのかを確認したくなります。そのためにはつぎのコードを動かしてみれば良いです。

package main

func main() {}

func F[T []int]() T {
	var x []int // []int型の変数xを宣言する。 []intはnamed typeではない。
	var t T
	t = x
	return t
}

このコードはつぎのPlaygroundで動作確認できます。何も表示されずにプログラムが完了することがわかると思います。

https://go.dev/play/p/i5UAGjIntwU

クイズから得られる知識を一言でまとめる

単一の型のみによる型制約を持つ型パラメータを型に持つ変数に、その単一の型の変数を代入できるのは、その型がnamed typeではないときであり、またそのときに限る。

結論

次のコードはコンパイルできません。

package main

func main() {}

func F[T int]() T {
	x := 1
	var t T
	t = x
	return t
}

https://go.dev/play/p/Qe8Bsbgk6EU

GitHubで編集を提案

Discussion