Goの基底型について

公開: 2025/07/27

Go

はじめに

Goのコア型が廃止されると聞いたので,そもそも基底型ってなんだっけ,の記事です.コア型については次の記事で書きます.

基底型の定義

基底型(underlying type)公式ドキュメントにおいて,次のように説明されています.

Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its declaration. For a type parameter that is the underlying type of its type constraint, which is always an interface.

1つずつ見ていきましょう.

Each type T has an underlying type

全ての型は基底型を持っています.

If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself

事前定義された型(bool型、数値型、文字列型など)または型リテラル(無名型)の基底型はそれ自身になります.

事前定義型の例

// 事前定義型の基底型は自分自身
bool        // 基底型: bool
int         // 基底型: int  
string      // 基底型: string

型リテラルの例

// 配列・スライス
[5]int      // 基底型: [5]int
[]string    // 基底型: []string

// マップ
map[string]int  // 基底型: map[string]int

// チャネル
chan int        // 基底型: chan int
<-chan string   // 基底型: <-chan string
chan<- bool     // 基底型: chan<- bool

// 構造体
struct {
    Name string
    Age  int
}              // 基底型: struct{Name string; Age int}

// インターフェース
interface {
    Read([]byte) (int, error)
}              // 基底型: interface{Read([]byte) (int, error)}

// 関数型
func(string) int    // 基底型: func(string) int

// ポインタ型
*int               // 基底型: *int

Otherwise, T's underlying type is the underlying type of the type to which T refers in its declaration.

それ以外の型の場合は,その型が定義で参照している型の基底型が対応します.

// 型A, B, Cの基底型は全て int
type A int
type B A
type C B

errorも,interfaceで定義されているので基底型はinterface{ Error() string }です.

For a type parameter that is the underlying type of its type constraint, which is always an interface.

型パラメータ(ジェネリクス)の場合,基底型はその型制約(type constraint)の基底型になります.型制約は常にインターフェース型です.

型制約とは

型制約(Type constraint は,Go 1.18で導入された,ジェネリクスにおいて型パラメータに対して使用可能な型を制限するための仕組みです(Haskellなどの型クラスによく似ています).型制約は必ずインターフェース型で定義されます.

// anyは全ての型を許可
func Print[T any](value T) {
    fmt.Println(value)
}

// 比較可能な型のみを許可する型制約
func Max[T comparable](a, b T) T {
    if a > b {  // comparableな型は==, !=演算子が使用可能
        return a
    }
    return b
}

// カスタム型制約の定義(型集合)
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64
}

func Add[T Numeric](a, b T) T {
    return a + b  // 数値型のみで加算演算が可能
}

型制約には以下の記法が使用されます:

  • any:全ての型を許可(interface{}のエイリアス)
  • comparable:比較演算子(==, !=)が使用可能な型
  • ~T:型Tを基底型とする全ての型(チルダ記法)
  • A | B:型AまたはBのユニオン型

その他,constraintsパッケージSignedOrderdなど複数の制約が用意されています.

基底型は型変換で役立つ

型変換の互換性判定

基底型が同じ型同士は,明示的な型変換が可能です:

type Celsius float64
type Fahrenheit float64

// 両方ともfloat64が基底型のため変換可能
var c Celsius = 25.0
var f Fahrenheit = Fahrenheit(c * 9/5 + 32)  // OK
f = c * 9/5 + 32 // これはダメ

基底型が異なる場合は直接変換できません:

type UserId int
type ProductId string

var uid UserId = 123
var pid ProductId = ProductId(uid)  // エラー:基底型が異なる

コンパイラの最適化

コンパイラは基底型を使用して型変換を最適化します.特に以下のような場面で効果を発揮します:

type MySlice []int

func process(s MySlice) []int {
    return []int(s)  // 基底型が同じため効率的な変換
}