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