reflect経由でgoのmethodを呼んだり存在を確認したりする

encoding/jsonパッケージの範囲を越えてJSONと戯れようとする。動的な何かが必要になる。そしてけっこうすぐにreflectパッケージに触れることになる。触りたくないけれど触る必要がある汚泥のような存在。それがreflect。

重い腰をあげてreflectと少し仲良くなるためにした試行錯誤のメモ。

概ね以下のことのやリ方についての文章になっている。

  • 定義されているメソッドの数を確認
  • メソッドの存在確認
  • メソッドを呼び出す

定義されているメソッドの数を確認

定義されているメソッドの確認は型に対して行う。型に対してということは reflect.Value ではなく reflect.Type を使うということ。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f *Foo) String() string {
    return "Foo"
}

func main() {
    {
        v := Foo{}
        rt := reflect.TypeOf(v)
        fmt.Println(rt.NumMethod(), rt.Kind(), rt.Name(), rt.String())
        // 0 struct Foo main.Foo
    }
    {
        v := &Foo{} // pointer
        rt := reflect.TypeOf(v)
        fmt.Println(rt.NumMethod(), rt.Kind(), rt.Name(), rt.String())
        // 1 ptr  *main.Foo
    }
    {
        v := Foo{}

        // Valueからpointerの型を作る
        rv := reflect.ValueOf(v)
        rt := reflect.PtrTo(rv.Type())

        fmt.Println(rt.NumMethod(), rt.Kind(), rt.Name(), rt.String())
        // 1 ptr  *main.Foo
    }
}

ついでに組み込みの型かどうかを調べる

reflect.Type.PkgPathを見るのが正解なのかもしれない。

type Type interface {
...
        // PkgPath returns a defined type's package path, that is, the import path
        // that uniquely identifies the package, such as "encoding/base64".
        // If the type was predeclared (string, error) or not defined (*T, struct{},
        // []int, or A where A is an alias for a non-defined type), the package path
        // will be the empty string.
        PkgPath() string
...
}

ためしにやってみる。悪くなさそう。

package main

import (
    "fmt"
    "reflect"
)

type MyInt int

func main() {
    {
        var z int
        rt := reflect.TypeOf(z)
        fmt.Printf("%q   %q\n", rt.String(), rt.PkgPath())
        // "int" ""
    }
    {
        var z MyInt
        rt := reflect.TypeOf(z)
        fmt.Printf("%q   %q\n", rt.String(), rt.PkgPath())
        // "main.MyInt"  "main"
    }
}

詳しい。

メソッドの存在確認

reflect.Type経由で名前で確認する。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f Foo) String() string {
    return "Foo"
}

type Bar struct{}

func main() {
    {
        v := Foo{}
        rt := reflect.TypeOf(v) // or reflect.ValueOf(v).Type()
        fmt.Println(rt.MethodByName("String"))
        // {String  func(main.Foo) string <func(main.Foo) string Value> 0} true
    }

    {
        v := Bar{}
        rt := reflect.TypeOf(v) // or reflect.ValueOf(v).Type()
        fmt.Println(rt.MethodByName("String"))
        // {  <nil> <invalid Value> 0} false
    }
}

ところでポインターにくっついたメソッドは取り出せない。まぁ型が違うので。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f *Foo) String() string {
    return "Foo"
}

func main() {
    {
        v := Foo{}
        rt := reflect.TypeOf(v) // or reflect.ValueOf(v).Type()
        fmt.Println(rt.MethodByName("String"))
        // {  <nil> <invalid Value> 0} false
    }
    {
        v := &Foo{}
        rt := reflect.TypeOf(v) // or reflect.ValueOf(v).Type()
        fmt.Println(rt.MethodByName("String"))
        // {String  func(*main.Foo) string <func(*main.Foo) string Value> 0} true
    }
}

interface経由で確認

上のようにメソッドの存在を確認するのではなく、インターフェイスが実装されているかで確認したほうがきれいな場合もあるかも。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
}

func (f *Foo) String() string {
    return "Foo"
}

func main() {
    {
        z := Foo{}
        var iface fmt.Stringer
        rt := reflect.TypeOf(z)
        fmt.Println(rt.Implements(reflect.TypeOf(&iface).Elem()))
        // false
    }
    {
        z := &Foo{}
        var iface fmt.Stringer
        rt := reflect.TypeOf(z)
        fmt.Println(rt.Implements(reflect.TypeOf(&iface).Elem()))
        // true
    }
}

インターフェイスreflect.Typeとして取り出す時に、TypeOf()Elem() を組み合わせるのがちょっとトリッキー。

以下のようなコードだとpanicする。

// NG
rt.Implements(reflect.TypeOf(iface))

まぁ確かに nil type (値と型のペアで保持していて型の部分がnilだとnil type)。

panic: reflect: nil type passed to Type.Implements

go-nutsとかはじめて覗いた(気がする)。

メソッドを呼び出す

メソッドを呼び出すのは値のほうなので reflect.Value 引数も戻り値も全部 []reflect.Value なことに注意。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Name string
}

func (Foo) String() string {
    return "Foo"
}
func (*Foo) Say() string {
    return "hello"
}

func main() {
    {
        v := Foo{Name: "xxx"}
        rv := reflect.ValueOf(v)
        method := rv.MethodByName("String")
        fmt.Printf("%[1]T    %[1]v\n", method.Call(nil))
        // []reflect.Value [Foo]

        fmt.Printf("%[1]T    %[1]v\n", method.Call(nil)[0].String())
        // string  Foo
    }

    {
        v := Foo{Name: "xxx"}
        rv := reflect.ValueOf(v)

        // make pointer
        rptrType := reflect.PtrTo(rv.Type())
        rptr := reflect.New(rptrType.Elem())
        rptr.Elem().Set(rv)

        method := rptr.MethodByName("Say")
        fmt.Printf("%[1]T    %[1]v\n", method.Call(nil))
        // []reflect.Value [hello]
    }
}

ちなみにpointerを期待した所でpointer以外からメソッドを呼び出すと以下のようなエラーが出る。

panic: reflect: call of reflect.Value.Call on zero Value

はい。

なのでわざわざ値を作ってからその値をセットしなくてはいけない。

       // make pointer
        rptrType := reflect.PtrTo(rv.Type())
        rptr := reflect.New(rptrType.Elem())
        rptr.Elem().Set(rv)

Sliceなどに対しては MakeSlice() という関数なので MakePtr() だと思うと New() というのはちょっとハマる。

さいごに

さいごにこのreflectのサンプル集がとても便利です。

:pray: このページだけ覚えて帰ってもらえるとそれだけでこの記事を読むより価値があると思います。

参考文献