title | emoji | type | topics | published | |
---|---|---|---|---|---|
[Go Quiz] 単一の型のみによる型制約を持つ型パラメータを型に持つ変数にその型の変数を代入できるか |
🐈 |
tech |
|
true |
この記事の目的は、次の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)に基づいて解説します。
次のコードはコンパイルできるでしょうか?
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
考えやすくするためにコメントをつけてみます。
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
:::message
xの型がintになることを説明するにはuntyped constant(型なし定数・型付けなし定数)の理解が必要です。
DQNEOさんのスライドが参考になると思います。
https://speakerdeck.com/dqneo/go-specification-untyped-constants
:::
:::message
Goジェネリクスの初歩的知識はGo言語のジェネリクス入門を参照してください。
:::
まずセクション全体を引用してみます。
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
に読み替えて読んでいくことになります。そこでV
をint
に置き換えたものを書き下し、それを確認していきます。
- 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は事前宣言された識別子
- 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
ではないのであてはまりません。
- xは事前宣言された識別子
- 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は型パラメータではないのであてはまりません。
:::message
identicalなどのさらっと説明してしまった項目もちゃんと仕様書に基づいて説明するにはもう少し記述を要しますが、今回は省略して書いています。
:::
よって全部あてはまりません。
このうち、
- int is not a named type, T is a type parameter, and x is assignable to each type in T's type set.
が特に説明を要するので、次にこれを説明します。
Go1.18(ジェネリクスが追加されたバージョン)から、仕様用語としてのnamed typeが追加されました。
Predeclared types, defined types, and type parameters are called named types.
これによると、
- 事前宣言された型
- defined types
- 型パラメータ
はまとめてnamed typeと呼ばれます。
:::message
defined typeの定義については次の記事で解説しています:
https://zenn.dev/nobishii/articles/defined_types
:::
これをみてから改めて代入可能性の文を読み直します。
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
}