Go言語の型と値の性質を理解する – Properties of Types and Values 備忘録
この記事は、Goの言語仕様の「Properties of types and values」を読んで学んだことを備忘録的にまとめたものです。
理解が間違っている箇所などあればご指摘いただけると幸いです。
読もうと思ったきっかけ
静的解析ツールを作成する学習を進める中で、Goの言語仕様に対する理解がまだ不足していると感じました。
一般的なパターンの検知だけでなく、あまり使われないケースや複雑な型の組み合わせにも対応するためには、型の性質や代入可能性、型同士の関係性を正確に理解する必要があります。
そのため、言語仕様を改めて読み直し、自分の中で整理する目的でこの記事を執筆しました。
型の種類
Goの型は以下のように分類されます:
-
基本型(Core Types)
-
bool
、int
、float64
、string
など、言語が提供するプリミティブ型。
-
-
複合型(Composite Types)
- 配列(Array)、スライス(Slice)、マップ(Map)、構造体(Struct)など。
-
インターフェース型(Interface Types)
- インターフェースを定義する型。
-
型定義と型エイリアス
- 新しい型を作成するための
type
キーワードや、既存型を参照するための型エイリアス。
- 新しい型を作成するための
Underlying Types(基底型)
Goのすべての型には「Underlying type(基底型)」があります。
基本ルール
-
宣言済みの基本型
Goの基本型(boolean, numeric, string)のUnderlying typeは、それ自体です。var b bool // Underlying type: bool
-
型リテラル
型リテラル(map、struct など)のUnderlying typeは、自分自身です。type MyMap map[string]int // Underlying type: map[string]int
-
型定義
型定義によって新しい型が作成されても、Underlying typeは共有されます。type A int // Underlying type: int type B A // Underlying type: int
-
型エイリアス
型エイリアスは元の型と完全に同一視され、Underlying typeも一致します。type AliasInt = int var a AliasInt var b int a = b // OK
Core Types(基本型)
Goの基本型(Core Types)は以下の通りです:
- 数値型:
int
、float64
、complex128
など - 論理型:
bool
- 文字列型:
string
- その他:
uintptr
これらの型は、Goの型システムの基盤となり、特定の演算や比較で重要な役割を果たします。
Type Identity(型の識別性)
型の識別性は以下のルールに従います:
-
名前付き型と無名型
名前付き型と無名型は、Underlying typeが同じでも異なる型として扱われます。type MyInt int var x MyInt var y int // x = y はコンパイルエラー
-
型リテラル
型リテラルが同一の型と認識される条件:- 構造体型: フィールド名、順序、型が一致する。
- スライス型、配列型: 要素型が一致し、配列の場合は長さも一致する。
- マップ型: キー型と値型が一致する。
- チャネル型: 方向と要素型が一致する。
- インターフェース型: メソッドセットが一致する。
-
インターフェース型の特例
名前付きインターフェース型であっても、メソッドセットが一致していれば相互に代入可能です。type ReadWriter interface { Read([]byte) (int, error) Write([]byte) (int, error) } type AnotherReadWriter interface { Read([]byte) (int, error) Write([]byte) (int, error) } var rw ReadWriter var arw AnotherReadWriter rw = arw // OK: メソッドセットが一致している
Assignability(代入可能性)
型が代入可能かどうかは、次の条件に基づいて決まります:
-
インターフェース型
インターフェースを実装している型は代入可能です。var w io.Writer w = os.Stdout // os.Stdout は io.Writer を実装している
-
型エイリアス
型エイリアスを使用すると、代入可能性が拡張されます。 -
型アサーション
型アサーションを使用することで、インターフェース型から具体的な型を取得できます。var w io.Writer = os.Stdout if rw, ok := w.(io.ReadWriter); ok { fmt.Println("w implements io.ReadWriter") }
-
型パラメータ
型パラメータの場合、型セットに基づいて代入可能性が判断されます。
Method Sets(メソッドセット)
型のメソッドセットは、その型に関連付けられたメソッドの集合を定義します。
メソッドセットは、特定の型でどのメソッドが利用可能かを決定します。
-
定義された型のメソッドセット
型自体に定義されたメソッドのみが含まれます。 -
ポインタ型のメソッドセット
ポインタ型には、ポインタレシーバーおよび値レシーバーのメソッドが含まれます。
メソッドセット例
type T struct{}
func (t T) ValueMethod() {}
func (t *T) PointerMethod() {}
var v T
var p *T
v.ValueMethod() // OK
p.PointerMethod() // OK
// 注意: 値型の変数にはポインタレシーバーのメソッドは利用不可
v.PointerMethod() // コンパイルエラー
参考記事
Discussion