Goの型システムと「基底型(underlying type)」の概念について解説します。
Goの型システムの特徴
Goはシステムプログラミングを念頭において設計された、強い型付け(strong typing)を持つ言語です。型システムには以下のような主要な特徴があります。
- 値とメソッドの決定: 型は、変数が保持できる値の集合と、その型に対して呼び出し可能な「メソッド集合」を決定します。
- 静的な型と動的な型: 変数には宣言時に指定される「静的な型(static type)」があります。一方、インターフェース型の変数は、実行時に代入された値に基づく「動的な型(dynamic type)」も持ち合わせます。
- 型の種類:
intやstringなどの「事前宣言された型」と、ユーザーがtypeキーワードで導入する「定義型(defined type)」があります。また、配列、スライス、マップ、構造体などの「複合型」は型リテラルを使って構築できます。 - 厳密な型区別: 定義型は、たとえ生成元が同じであっても、他のあらゆる型とは異なるものとして厳密に区別されます。例えば、
intと、それを元に作ったtype MyInt intは異なる型として扱われます。
「基底型(underlying type)」の概念
Goのすべての型 T には、必ず一つの「基底型(underlying type)」が存在します。基底型は以下のルールに従って決定されます。
Tが事前宣言された型(ブール型、数値型、文字列型など)であるか、型リテラル([]intや*stringなど)である場合、対応する基底型は T 自身となります。- それ以外の場合(つまり
Tがtype宣言によって定義された新しい型の場合)、T の基底型は、宣言内で参照している元の型の基底型となります。
たとえば、type MyInt int と型定義した場合、MyInt は独自の定義型となりますが、その基底型は int です。
基底型の役割と「代入可能性(Assignability)」
基底型の概念が最も重要になるのは、「ある値を変数に代入できるか(代入可能性)」をコンパイラが判断する場面です。
Goでは、値 x (型 V)を型 T の変数に代入できる条件の一つとして、以下が定められています。
- V と T が同一の基底型を持ち、かつ V と T の少なくとも一方が定義型(defined type)ではないこと。
具体例
- 代入できるケース:
type MyIntSlice []intと定義されている場合を考えます。MyIntSlice(定義型)と[]int(型リテラル)の基底型は、どちらも[]intで同一です。そして、[]intは型リテラルであり「定義型ではない」ため、条件を満たし、[]intの値をMyIntSlice型の変数に直接代入することが可能です。 - 代入できないケース:
type MyInt intと定義した場合、MyIntとintはどちらも基底型がintで同一です。しかし、int(事前宣言された定義型)もMyInt(ユーザーが作成した定義型)も両方とも定義型であるため、条件を満たさず、変数同士を直接代入することはできません(コンパイルエラーになります)。
このように、Go言語では「基底型」と「それが定義型かどうか」という概念を組み合わせることで、厳格な型安全性を保ちつつ、スライスやマップなどのリテラル表現を使った柔軟なコーディングを可能にしています。