Site cover image

Site icon imageやぎにいのノート

やぎにいちゃんのノートパッド

💻Golangにおける型アサーションと型キャスト

Golangを今年から業務で使うために学習しつつ、プロダクションコードを書いているが初学者にとって型アサーションと型キャストが正直「どっちも型を変換してくれてるやないかい!」となりがちだったので、改めてまとめてみる。

A Tour of Go

A Tour of Goは最初の最初に1通りやったが、改めて型アサーションと型変換について。

書いてあることをまとめてみると

Type assertions

型アサーションは、インターフェース値に対して具体の型へのアクセスができるようにアサーションする。

Type conversions

単純に型を変換する。

まとめてしまうと、アサーションはインターフェースに行うやつで、キャスト(型変換と呼ばれている)は値を直接そのまま別の型に変換するらしい。

自分の頭の中のイメージはこの時点で別の言語(Kotlin)だけど

interface Foo {
	fun FooFunc()
}

class Bar: Foo {
	override fun FooFunc() { println("FooFunc impl in Bar class") }
	fun BarFunc() { println("BarFunc") }
}

class Baz: Foo {
	override fun FooFunc() { println("FooFunc impl in Baz class") }
	fun BazFunc() { println("BazFunc") }
}

みたいなインターフェースとそれを実装しているクラスがあった場合に、汎用的に Foo を受け取るようなメソッドがあるけど、場合によって BarFunc, BazFunc をそのメソッドの中で呼びたいときに型アサーションをしてあげると具体の実装が呼べるよという理解。(たぶん)

型アサーション

以下のようにして型アサーションを行ってメソッドを呼ぶ事ができる。

type Foo interface {
	FooFunc()
}

func doFunc(i interface{}) {
	f := i.(Foo)
	f.FooFunc()
}

他の型で FooFunc を実装したときも doFunc にその型を渡せば実行できるようになる。

type Bar struct {
	TypeName string
}

func (b *Bar) FooFunc() {
	fmt.Println("call FooFunc in %s, b.TypeName)
}

func main() {
	b := &Bar{
		TypeName: "Bar",
	}
	doFunc(b) // -> "call FooFunc in Bar"
}

逆にここで FooFunc を実装していないオブジェクトを doFunc に渡してしまうと型アサーションが失敗してpanicを起こす。

panicは困るので、ちゃんと型変換が成功したのかどうかを調べる構文がある。(Mapのキーがあるかどうか確認する構文と同じ。

func doFunc(i interface{}) {
	if f, ok := i.(Foo); ok {
		f.FooFunc()
	} else {
		fmt.Println("%T does not meet the requirements.", i) 
	}
}

func main() {
	doFunc(&Bar{"Bar"}) // -> call FooFunc in Bar
	doFunc("Hoge") // -> string does not meet the requirements.
}

型変換

別の型に変換する場合、基本的には関数が提供されておりそれを使用して変換を行う。

intNum := 1
floatNum := float64(intNum) // intからfloatに型変換される
fmt.Println(floatNum) // -> 1.0

numStr := "1"
num, err := strconv.Atoi(numStr) // strconvパッケージでAtoiというStringからintに変換するメソッドが提供されている
if err != nil {
	fmt.Println(err)
} else {
	fmt.Println(num) // -> 1
}

まとめ

型キャスト(型変換)は値を別の型の値に変換する。型アサーションはインターフェースに対して具体的な型になれるかどうかを確認したり、そのままその具体な型として扱う(関数を呼んだり)することができる。