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