🦎

Go言語の型と値の性質を理解する – Properties of Types and Values 備忘録

2025/01/01に公開

この記事は、Goの言語仕様の「Properties of types and values」を読んで学んだことを備忘録的にまとめたものです。
理解が間違っている箇所などあればご指摘いただけると幸いです。

読もうと思ったきっかけ

静的解析ツールを作成する学習を進める中で、Goの言語仕様に対する理解がまだ不足していると感じました。
一般的なパターンの検知だけでなく、あまり使われないケースや複雑な型の組み合わせにも対応するためには、型の性質や代入可能性、型同士の関係性を正確に理解する必要があります。

そのため、言語仕様を改めて読み直し、自分の中で整理する目的でこの記事を執筆しました。

型の種類

Goの型は以下のように分類されます:

  • 基本型(Core Types)
    • boolintfloat64string など、言語が提供するプリミティブ型。
  • 複合型(Composite Types)
    • 配列(Array)、スライス(Slice)、マップ(Map)、構造体(Struct)など。
  • インターフェース型(Interface Types)
    • インターフェースを定義する型。
  • 型定義と型エイリアス
    • 新しい型を作成するための type キーワードや、既存型を参照するための型エイリアス。

Underlying Types(基底型)

Goのすべての型には「Underlying type(基底型)」があります。

基本ルール

  1. 宣言済みの基本型
    Goの基本型(boolean, numeric, string)のUnderlying typeは、それ自体です。

    var b bool // Underlying type: bool
    
  2. 型リテラル
    型リテラル(map、struct など)のUnderlying typeは、自分自身です。

    type MyMap map[string]int // Underlying type: map[string]int
    
  3. 型定義
    型定義によって新しい型が作成されても、Underlying typeは共有されます。

    type A int  // Underlying type: int
    type B A    // Underlying type: int
    
  4. 型エイリアス
    型エイリアスは元の型と完全に同一視され、Underlying typeも一致します。

    type AliasInt = int
    var a AliasInt
    var b int
    a = b // OK
    

Core Types(基本型)

Goの基本型(Core Types)は以下の通りです:

  • 数値型:intfloat64complex128 など
  • 論理型: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(代入可能性)

型が代入可能かどうかは、次の条件に基づいて決まります:

  1. インターフェース型
    インターフェースを実装している型は代入可能です。

    var w io.Writer
    w = os.Stdout // os.Stdout は io.Writer を実装している
    
  2. 型エイリアス
    型エイリアスを使用すると、代入可能性が拡張されます。

  3. 型アサーション
    型アサーションを使用することで、インターフェース型から具体的な型を取得できます。

    var w io.Writer = os.Stdout
    if rw, ok := w.(io.ReadWriter); ok {
        fmt.Println("w implements io.ReadWriter")
    }
    
  4. 型パラメータ
    型パラメータの場合、型セットに基づいて代入可能性が判断されます。

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() // コンパイルエラー

参考記事

https://go.dev/ref/spec#Properties_of_types_and_values
https://speakerdeck.com/dqneo/go-language-underlying-type
https://zenn.dev/hsaki/articles/gospecdictionary
https://hiwane.github.io/gospec-ja/
https://zenn.dev/nobishii/articles/99a2b55e2d3e50

Discussion