覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - 空インターフェースと型アサーション

Go言語には、全ての型と互換性を持っているinterface{}型(空インターフェース)というものが存在しています。

たとえば以下のように、interface{}で宣言した変数にはどんな型の値でも代入可能です

var obj interface{}

obj = 123                                                              // int
obj = "str"                                                            // string
obj = []string{"linux", "windows", "android"}                          // slice
obj = make(chan string)                                                // channel
obj = func(val int) string { return fmt.Sprintf("number is %d", val) } // function


また、引数の型をinterface{}にすると、どんな型の値でも受け取ることができる関数を記述できます

func anyExec(any interface{}) {
    ・・・
}

func main() {
    anyExec(12)
    anyExec("hello")
    anyExec([]string{"cat", "dog"})
    anyExec([2]string{"hello", "world"})
}


型アサーション

どんな型の値でも受け取れるinterface{}ですが、interface{}型の引数で受け渡された値は、元の型の情報が欠落しています。
(元の型の値を操作するための関数等を実行できません)

Go言語ではこのような局面で利用するための型アサーションを提供しており、型アサーションにより実体の型が何であるかを動的にチェックすることができます。(以下構文を使用します)

<変数>.(<型>)


基本的に以下のように記述します。

value, ok := <変数>.(<型>)

1番目の変数には型アサーション成功時に実際の値が格納されます。2番目の変数には型アサーションの成功の有無(true/false)が格納されます。


以下はサンプルの関数です。引数に型アサーションを用いて型に応じた処理の分岐をさせています

func printIf(src interface{}) {
    if value, ok := src.(int); ok {
        fmt.Printf("parameter is integer. [value: %d]\n", value)
        return
    }

    if value, ok := src.(string); ok {
        value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる
        fmt.Printf("parameter is string. [value: %s]\n", value)
        return
    }

    if value, ok := src.([]string); ok {
        value = append(value, "unknown") // 対象がsliceなのでAppendができる
        fmt.Printf("parameter is slice string. [value: %s]\n", value)
        return
    }

    fmt.Printf("parameter is unknown type. [valueType: %T]\n", src)
}


■呼び出し側

func main() {
    printIf(12)
    printIf("hello")
    printIf([]string{"cat", "dog"})
    printIf([2]string{"hello", "world"})
}

■実行結果

parameter is integer. [value: 12]
parameter is string. [value: HELLO]
parameter is slice string. [value: [cat dog unknown]]
parameter is unknown type. [valueType: [2]string]


型switch

型アサーションと分岐を組み合わせた処理を手軽に記述するための型switchが提供されています。
型switchを利用すると前述のprintIf関数は以下のように記述できます

func printIf(src interface{}) {
    switch value := src.(type) {
    case int:
        fmt.Printf("parameter is integer. [value: %d]\n", value)
    case string:
        value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる
        fmt.Printf("parameter is string. [value: %s]\n", value)
    case []string:
        value = append(value, "<不明>") // 対象がsliceなのでAppendができる
        fmt.Printf("parameter is slice string. [value: %s]\n", value)
    default:
        fmt.Printf("parameter is unknown type. [valueType: %T]\n", src)
    }
}


■呼び出し側

func main() {
    printIf(120)
    printIf("bye")
    printIf([]string{"apple", "orange", "banana"})
    printIf([2]string{"good", "bye"})
}

■実行結果

parameter is integer. [value: 120]
parameter is string. [value: BYE]
parameter is slice string. [value: [apple orange banana <不明>]]
parameter is unknown type. [valueType: [2]string]



関連エントリ