いろいろ備忘録日記

主に .NET とか Java とか Python絡みのメモを公開しています。最近Go言語勉強中。

Goメモ-30 (型検証, Type Assertions, Tour of Go)

概要

Tour of Go の - Type Assertions についてのサンプル。

tour.golang.org

今回は、型検証について。

Goにも、他の言語でもある型検証の仕組みが存在します。

オブジェクト指向な言語では、よくインターフェースの型を特定の具象型に変換したりすることがあります。

Goにも、インターフェースを特定の具象型、または、別のインターフェース型に変換することがあります。

型検証の機能は、このときに使います。よく使うのは、interface{}を別の型に変換するときとかが多いですね。

基本的な構文としては、以下のようになります。

t := i.(T)

ちょっと、独特な書き方ですね。インターフェース iT 型に変換可能な場合は t が返ります。

変換不可能な場合、ランタイムエラーとなります。

なので、型が確実に変換可能であるとわかっている場合はこれでいいのですが、そうでない場合もよくあります。

そのような場合は、以下のように書けます。というか、通常ほぼこっちを使うと思います。

t, ok := i.(T)

変換できた場合は ok に、true が設定されます。

コードで書くとこんな感じです。

type (
    I interface {
        F()
    }

    Impl struct {
    }
)

func (i Impl) F() {
    fmt.Println("Impl.F()")
}

という定義をしている状態で、

var v I = Impl{}
v.F()

if t, ok := v.(Impl); ok {
    t.F()
}

とすると、型検証により t には Impl 型 が入ります。

ただし、よく間違えてしまう注意点があって

インターフェースの実装をポインタレシーバで実装している場合、型検証するときもポインタ型で指定しないといけない

というのがあります。

つまり、インターフェースの実装を

func (i *Impl) F() {
    fmt.Println("Impl2.F()")
}

というように、ポインタレシーバで定義している場合

if t, ok := v.(Impl); ok {
    t.F()
}

ってやるとコンパイルエラーとなります。 (impossible type assertion)

if t, ok := v.(*Impl); ok {
    t.F()
}

ってやればオッケイ。

サンプル

package tutorial

import (
    "fmt"
    "strings"
)

//noinspection GoUnusedType
type (
    ifGoTour20 interface {
        Value() string
    }

    ifGoTour20Impl struct {
        V string
    }

    ifGoTour20NotImpl struct {
        V string
    }
)

// impl fmt.Stringer
func (i *ifGoTour20Impl) String() string {
    return i.Value()
}

// impl tutorial.ifGoTour20
func (i *ifGoTour20Impl) Value() string {
    return strings.ToUpper(i.V)
}

// TypeAssertion は、 Tour of Go - Type assertions (https://tour.golang.org/methods/15) の サンプルです。
func TypeAssertion() error {
    // ------------------------------------------------------------
    // Go言語の型検証 (Type Assertions)
    // 他の言語でもある型検証の仕組みがGo言語にも当然ある.
    // インターフェースに設定されている値の型を具象型または別のインターフェース
    // 型に変換する際に利用する.
    //
    // 基本的な構文としては以下のようになる
    //   t := i.(T)
    // インターフェースに設定されている値がT型に変換可能な場合はtが返る.
    //
    // この場合、変換できない場合にランタイムエラーとなる.
    // 変換可能かどうかがはっきりしていない場合は、マップにキーが存在するかを
    // 確認するのと同じような形で以下のように書く。
    //   t, ok := i.(T)
    // 変換できた場合は ok に true が設定される.
    //
    // 注意点として
    //         type I interface {
    //             V() string
    //         }
    //
    //         type Impl struct {
    //         }
    //
    //         func (i *Impl) V() string {
    //             return "helloworld"
    //     }
    // という定義になっている状態でインターフェース I に具象型 Impl が
    // 設定されているとする。この場合、IからImplに変換する場合には
    //         i.(Impl)
    // とするとコンパイルエラーとなる。具象型 Impl で実装している
    // インターフェースのメソッドは、ポインタレシーバーを受け取る様になっている
    // ため、正しく変換するには、以下のように書く。
    //         i.(*Impl)
    // ------------------------------------------------------------
    var (
        i interface{} = "helloworld"
    )

    // 文字列型に変換
    if t, ok := i.(string); ok {
        fmt.Printf("[i.(string)] %T\t%v\n", t, t)
    }

    // 数値に変換
    // 変換できないので、ok は false になる
    if t, ok := i.(int); ok {
        fmt.Printf("[i.(int)] %T\t%v\n", t, t)
    }

    // 独自で定義したインターフェースの場合でも同じ
    var (
        impl            = &ifGoTour20Impl{V: "helloworld"}
        i2   ifGoTour20 = impl
    )

    // 具象型へ変換
    if t, ok := i2.(*ifGoTour20Impl); ok {
        fmt.Printf("[i2.(*ifGoTour20Impl)] %T\t%v\n", t, t)
    }

    // インターフェースを実装していない型に変換しようとすると
    // impossible type assertion: *ifGoTour20NotImpl does not implement
    // ifGoTour20 (missing Value method)
    // とコンパイルエラーとなる
    //if t, ok := i2.(*ifGoTour20NotImpl); ok {}

    // 変換元にインターフェース以外を設定すると
    // invalid type assertion: impl.(ifGoTour20) (non-interface type *ifGoTour20Impl on left)
    // とコンパイルエラーとなる.
    //if t, ok := impl.(ifGoTour20); ok {}

    // 別のインターフェースへの変換も出来る
    if t, ok := i2.(fmt.Stringer); ok {
        fmt.Printf("[i2.(fmt.Stringer)] %T\t%v\n", t, t)
    }

    // interface{} への変換は当然成功する
    if t, ok := i2.(interface{}); ok {
        fmt.Printf("[i2.(interface{})] %T\t%v\n", t, t)
    }

    return nil
}

try-golang/tutorial_gotour_21_typeassertion.go at master · devlights/try-golang · GitHub

実行すると以下な感じ。

[Name] "tutorial_gotour_type_assertion"
[i.(string)] string     helloworld
[i2.(*ifGoTour20Impl)] *tutorial.ifGoTour20Impl HELLOWORLD
[i2.(fmt.Stringer)] *tutorial.ifGoTour20Impl    HELLOWORLD
[i2.(interface{})] *tutorial.ifGoTour20Impl     HELLOWORLD

過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com