Type Switch

interface{}型で宣言された変数の値の具体的な型により処理を分岐させたい、というケースが考えられる。Type Switch という仕組みでそれを実現できる。switch - case 文を使って、型ごとの処理内容を case 内に記述する。

公式ドキュメントの Effective Go にもきちんと記載されているのだが、初めて見たとき少し戸惑うので一応メモ。


例えば、ある値に「かけ算」をする以下のような関数を作るとする。

  • 数値(int)が渡された場合は、その数に指定された数を掛けた結果を返す。
  • 文字列(string)が渡された場合は、それを指定された回数だけ繰り返す。

ソースはこんな感じになる。(簡略化するため int, string 以外では nil を返すものとする)

package main

import (
	"fmt"
	"strings"
)

func multiple(val interface{}, times uint) interface{} {
	fmt.Printf("Type of val is %T\n", val)

	switch val := val.(type) {
	case int:
		return val * int(times)
	case string:
		return strings.Repeat(val, int(times))
	default:
		fmt.Printf("[WARN] Can't handle this type of value : %T\n", val)
		return nil
	}
}

func main() {
	fmt.Printf("3 * 5 = %v\n\n", multiple(3, 5))
	fmt.Printf("'逃げちゃダメだ' * 5 = %v\n\n", multiple("逃げちゃダメだ", 5))
	fmt.Printf("1.5 * 5 = %v\n\n", multiple(1.5, 5))
}

実行結果

Type of val is int
3 * 5 = 15

Type of val is string
'逃げちゃダメだ' * 5 = 逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ

Type of val is float64
[WARN] Can't handle this type of value : float64
1.5 * 5 = <nil>


ポイントを一つずつ見ていくと、

func multiple(val interface{}, times uint) interface{} {

まず、引数 val (掛けられる数)を interface{} 型と宣言し、任意の型の値を受け取れるようにする。
引数 times は、掛ける数。

    switch val := val.(type) {

val.(type) というのが一見奇妙に見えるが、これは普通のキャスト。int 型にキャストするために val.(int) とするのと同じ。
これにより、interface{}型として扱われていた val が、本来の型で再代入されることになる。val の本来の型が int であるなら、 val := val.(type) によりはじめて int 型として扱われる。
(補足も参照)

なおかつ switch 文の式としてこのように書くことにより、 interface{} 型として受け取った val の本来の型が何かを判定し、case で分岐させることができる。


(補足) 以下は自分でよく理解できていないところ。

キャストを行わないと、例えば

        //  val := val.(type) を以下のように変更
	switch val.(type) {

のようにすると、val が interface{} 型のままではかけ算に使えないといったコンパイルエラーとなる。

# command-line-arguments
./multiple.go:11: invalid operation: val * int(times) (mismatched types interface {} and int)
./multiple.go:13: cannot use val (type interface {}) as type string in argument to strings.Repeat: need type assertion

ところが、結果の Type of val is ... からも分かる通り、val は別に interface{} 型ではなく、引数として受け取った時点で具体的な型を持っている。
だったらキャストは不要なんじゃないかと思うのだが、コンパイル時にそれを保証できないということなのかなぁ。
そこはちとモヤモヤしてる。