コンテンツにスキップ

Go/underlying type

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

Goにおける underlying type(基底型)とは、Goの型システムにおける重要な概念の一つで、型が最終的にどのように表現されるかを示すものです。Goの型は、名前付き型(named type)と型リテラル(type literal)に大きく分けられますが、それぞれの型は必ず一つの underlying type を持ちます。

定義と規則

[編集]

Goの仕様によると、各型の underlying type は以下のように定義されます。

  • プリミティブ型 (basic type): bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128underlying type は、その型自身です。
  • 配列型 (array type): 配列要素型の underlying type を持ちます。例えば、[3]intunderlying typeint ではありません。[3]intunderlying type[3]int 自身です。
  • スライス型 (slice type): 要素型の underlying type を持ちます。例えば、[]intunderlying type[]int 自身です。
  • 構造体型 (struct type): 構造体型の underlying type は、その構造体型自身です。

ポインタ型 (pointer type): ベース型の underlying type を持ちます。例えば、intunderlying type*int 自身です。

  • 関数型 (function type): 関数型の underlying type は、その関数型自身です。
  • インターフェース型 (interface type): インターフェース型の underlying type は、そのインターフェース型自身です。
  • マップ型 (map type): キー型と要素型の underlying type を持ちます。例えば、map[string]intunderlying typemap[string]int 自身です。
  • チャネル型 (channel type): 要素型の underlying type を持ちます。例えば、chan intunderlying typechan int 自身です。
  • 型名 (named type): 既存の型に名前をつけた型(type MyInt int など)の underlying type は、その元になった型の underlying type と同じになります。

具体例

[編集]
package main

import "fmt"

type Celsius float64
type Fahrenheit float64

func main() {
	var c Celsius = 25.0
	var f Fahrenheit = 77.0
	var i int = 10
	var s []int

	fmt.Printf("Type of c: %T, Underlying type of c: %T\n", c, underlyingType(c))     // Type of c: main.Celsius, Underlying type of c: float64
	fmt.Printf("Type of f: %T, Underlying type of f: %T\n", f, underlyingType(f))     // Type of f: main.Fahrenheit, Underlying type of f: float64
	fmt.Printf("Type of i: %T, Underlying type of i: %T\n", i, underlyingType(i))     // Type of i: int, Underlying type of i: int
	fmt.Printf("Type of s: %T, Underlying type of s: %T\n", s, underlyingType(s))     // Type of s: []int, Underlying type of s: []int
	fmt.Printf("Type of [3]int: %T, Underlying type of [3]int: %T\n", [3]int{}, underlyingType([3]int{})) // Type of [3]int: [3]int, Underlying type of [3]int: [3]int
}

// 型の underlying type をリフレクションを使って取得する関数 (あくまで説明用)
func underlyingType(v interface{}) interface{} {
	return fmt.Sprintf("%T", v) // 実際には reflect.TypeOf(v).Kind() などを使う
}

underlying type の重要性

[編集]

underlying type は、Goの型システムの理解において非常に重要です。主に以下の点でその重要性が現れます。

  • 型変換 (Type Conversion): 型変換が可能なのは、underlying type が同じであるか、特定のルールに従う場合に限られます。例えば、CelsiusFahrenheitunderlying typefloat64 なので、明示的な型変換が可能です。しかし、intfloat64 のように underlying type が異なる場合は、明示的な型変換が必要です。
  • 比較 (Comparison): 比較演算子 (==, !=, <, >, <=, >=) が適用できるのは、同じ underlying type を持つ型同士、または特定のインターフェース型と具象型の間など、Goの仕様で定められた条件を満たす場合に限られます。
  • 型制約 (Type Constraints) (Go 1.18以降): ジェネリクスにおける型制約では、型パラメータが満たすべき型の集合を定義しますが、その際に underlying type が重要な役割を果たすことがあります。(Go 1.18 - Go 1.24 における core type の概念とも関連していました。)
  • 型同一性: 2つの型が同じであるかどうかを判断する際に、名前だけでなく underlying type も考慮されます。

まとめ

[編集]

underlying type は、Goの型がどのように内部的に表現され、どのように相互に変換・比較できるかを理解するための基礎となる概念です。名前付き型は、コードの可読性や意味合いを高めるために便利ですが、型の本質的な性質は underlying type によって決定されると言えます。