はじめに
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パッケージでSigned
やOrderd
など複数の制約が用意されています.
基底型は型変換で役立つ
型変換の互換性判定
基底型が同じ型同士は,明示的な型変換が可能です:
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) // 基底型が同じため効率的な変換
}