はじめに
最近reflectパッケージを使う機会があったので、その時どう書けばいいか調べたことをメモ書き。
気が向いたら更新してく。
面倒なのでimportとかは端折る。
reflect パッケージ
なにものなのかは godoc を見る。
DeepL翻訳様様で翻訳した内容↓
reflectパッケージは、プログラムが任意の型を持つオブジェクトを操作することを可能にする、ランタイムリフレクションを実装しています。典型的な使い方は,静的な型interface{}を持つ値を受け取り, TypeOfを呼んで動的な型情報を取り出し,それをTypeで返すというものです.
ValueOfを呼び出すと、実行時データを表すValueが返される。ZeroはTypeを取り、その型に対するゼロ値を表すValueを返す。
godocにも書かれているが https://go.dev/blog/laws-of-reflection も見てみると良さそう。
いろいろ使い方
Type/Valueを取得
interface{}
を扱う場合の基本はここから始まる。
func a(v interface{}) {
rt := reflect.TypeOf(v) // return reflect.Type
rv := reflect.ValueOf(v) // return reflect.Value
// do something...
}
型を判定して分岐処理をする
reflect
パッケージで定数化されている型はGitHubの reflect/type.go を参照。
func a(rt reflect.Type) {
switch rt.Kind() {
case reflect.String:
// string型の場合の処理
case reflect.Struct:
// 構造体の場合の処理
case reflect.Ptr:
// ポインタの場合の処理
default:
// いずれの型でもない
}
}
reflect.Value
でも同様にKind()
を呼び出すことで型判定が可能。
(値がポインタでなければ基本的には問題なと思う。たぶん。)
func a(rv reflect.Kind) {
switch rv.Kind() {
case reflect.String:
// string型の場合の処理
default:
// いずれの型でもない
}
}
型を判定して分岐処理をする(ポインタを考慮)
reflect.Type
のElem()
を実行すれば良い。
※Array
, Chan
, Map
, Ptr
, Slice
以外の型に対してElem()
を呼び出すとpanicが発生する。
func a(rt reflect.Type) {
switch rt.Kind() {
case reflect.String:
fmt.Println("string")
case reflect.Ptr:
fmt.Println("pointer")
a(rt.Elem())
default:
fmt.Printf("other(%s)", rt.Kind())
}
}
reflect.Value
でも同様の処理を書くことは可能だがnil値の考慮が必要になる。
これはNGな例
func main() {
var s *string
a(reflect.ValueOf(s))
}
func a(rv reflect.Value) {
switch rv.Kind() {
case reflect.String:
fmt.Println("string")
case reflect.Ptr:
fmt.Println("pointer")
a(rv.Elem())
default:
fmt.Printf("other(%s)", rv.Kind())
}
}
このコードを実行すると出力は以下のようになる。
nil値のポインタ型に対してElem()
を実行した場合、Kind()
メソッドが返す値はreflect.Invalid
になるので、おそらく書いた本人が意図した挙動にはならない。
$ go run main.go
pointer!!
other(invalid)
reflect.Ptr
の場合にIsNil()
を呼び出すことでnil値か判定してあげれば同じようなことができないわけではない。
func a(rv reflect.Value) {
switch rv.Kind() {
case reflect.String:
fmt.Println("string")
case reflect.Ptr:
fmt.Println("pointer")
if rv.IsNil() {
fmt.Println("nil!!")
return
}
a(rv.Elem())
default:
fmt.Printf("other(%s)", rv.Kind())
}
}
Array, Sliceの型を取得
例えばSliceに設定可能な型を取得したい場合、Elem().Kind()
で取得できる。
var s []string
rt := reflect.TypeOf(s)
fmt.Println(rt.Elem().Kind()) // string
rv := reflect.ValueOf(s)
fmt.Println(rv.Type().Elem().Kind()) // string
reflect.Valueから値を取り出す
プリミティブ型
取得したい型に対応するメソッドが用意されているので実行すれば良い。
※String()
以外のメソッドは実際の値の型と異なるメソッドを呼び出すとpanicが発生する。
func a(rv reflect.Value) {
switch rv.Kind() {
case reflect.Bool:
fmt.Println(rv.Bool())
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
fmt.Println(rv.Int())
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
fmt.Println(rv.Uint())
case reflect.Float32, reflect.Float64:
fmt.Println(rv.Float())
case reflect.String:
fmt.Println(rv.String())
default:
fmt.Printf("other(%s)", rv.Kind())
}
}
String()
はpanicを発生させることはないが、値がstring以外の型の場合は <int Value>
のような文字列が返ってくる。
設定された値を文字列として出力したい場合にString()
を使用すると意図しない結果になるので注意が必要。
それぞれの型でstringに変換するような処理を実装してあげる。
func a(rv reflect.Value) {
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
fmt.Println("true")
} else {
fmt.Println("false")
}
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
fmt.Println(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
fmt.Println(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
fmt.Println(strconv.FormatFloat(rv.Float(), 'f', -1, 32))
case reflect.Float64:
fmt.Println(strconv.FormatFloat(rv.Float(), 'f', -1, 64))
case reflect.String:
fmt.Println(rv.String())
default:
fmt.Printf("other(%s)", rv.Kind())
}
}
ただ文字列になればいいだけなら別にこれでも良いが。
func a(rv reflect.Value) {
fmt.Printf("%v", rv.Interface())
}
Array、Slice
SliceとArrayの使い方は基本的に同じ。
Len()
を呼び出すと長さが取得できるのでforで回しながらIndex()
を呼び出して値を取得していく。
Index()
はreflect.Value
を返すので、型によって処理分岐したければKind()
を使う。
ちなみにCap()
を呼び出すとcapacityを取得できる。
※Len()
とCap()
はArray
, Chan
, Slice
型以外の場合panicが発生する。
※Index()
はArray
, Slice
, String
型以外の場合panicが発生する。
func a(rv reflect.Value) {
fmt.Println(rv.Len())
fmt.Println(rv.Cap())
for i := 0; i < rv.Len(); i++ {
fmt.Println(rv.Index(i).String())
}
}
Map
MapRange()
を呼び出して、要素がなくなるまでforで回していく。
Mapのキーと値はそれぞれreflect.MapIter
のKey()
, Value()
で取得可能。
KeyとValuえはともにreflect.Value
を返すので、型によって処理分岐したければKind()
を使う。
※MapRange()
はMap
型以外の場合panicが発生する。
func a(rv reflect.Value) {
iter := rv.MapRange() // return *reflect.MapIter
for iter.Next() {
fmt.Printf("key: %v, value: %v", iter.Key(), iter.Value())
}
}
構造体
NumField()
で構造体が持つフィールド数を取得し、forで回していく。
フィールドの情報を取得するにはType()
で一度reflect.Type
を取得してから、Field()
を実行する必要がある。
フィールドの値を取得するにはFieldByName()
を使用する(戻り値はreflect.Value
)
※NumField()
, Field()
FieldByName()
はStruct
型以外の場合panicが発生する。
func a(rv reflect.Value) {
for i := 0; i < rv.NumField(); i++ {
f := rv.Type().Field(i)
fv := rv.FieldByName(f.Name)
fmt.Printf("field name: %s, value: %v", f.Name, fv)
}
}
その他構造体のフィールドから情報を取るメソッド
タグを取得
Tag
フィールドのGet()
を実行する。
タグは文字列で帰ってくるので、あとは自分が使いたいようにパースしたりして使う。
encoding/json の実装を参考にしてみると良いかも。
func main() {
a(reflect.ValueOf(struct {
a string `json:"abcde"`
}{a: "aaaa"})) // tag: "abcde"
a(reflect.ValueOf(struct{ a string }{a: "aaaa"})) // tag: ""
}
func a(rv reflect.Value) {
for i := 0; i < rv.NumField(); i++ {
f := rv.Type().Field(i)
fmt.Printf("field name: %s, tag: %v\n", f.Name, f.Tag.Get("json"))
}
}
フィールドがPublicかPrivateか判定
IsExported()
を使う
encoding/json でも使っている。
func main() {
a(reflect.ValueOf(struct{ A string }{A: "aaaa"})) // exported: true
a(reflect.ValueOf(struct{ a string }{a: "aaaa"})) // exported: false
}
func a(rv reflect.Value) {
for i := 0; i < rv.NumField(); i++ {
f := rv.Type().Field(i)
fmt.Printf("field name: %s, exported: %v", f.Name, f.IsExported())
}
}
interface{}
interface{}
を返すやつ
(TODO: 使ってないので雑)
func a(rv reflect.Value) {
fmt.Println(rv.Interface())
}
reflect.Valueに値を設定する
reflectを使用して元の値を書き換えるためには https://go.dev/blog/laws-of-reflection の第3法則
で説明されている内容が大事なポイント。
- To modify a reflection object, the value must be settable.
上記のように書かれています。
DeepL翻訳様様で翻訳した内容↓
- Reflectionオブジェクトを変更するには、その値が設定可能でなければなりません
詳細な内容は https://go.dev/blog/laws-of-reflection を見たほうが良い。
一応雑に書くとこんな感じ
以下のコードはx
のコピーがreflect.ValueOf
に渡るため
reflect.ValueOf
の引数として生成されるインターフェース値はx
そのものではない。
var x float64 = 3.4
v := reflect.ValueOf(x)
そのため以下のコードがもし成功したとしても、元の変数x
の値は変わることはない。
そのような挙動は混乱を招き、役に立たないため違反であるため、panicが発生する。
v.SetFloat(7.1)
値が設定可能であるかを判定するには CanSet()
メソッドを使用すれば良い。
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println(v.CanSet()) // false
値が設定可能であるとするためには、ValueOf()
に変数x
のポインタを渡す必要がある。
var x float64 = 3.4
v := reflect.ValueOf(&x) // --> set pointer
fmt.Println(v.CanSet()) // false
さらにポインタの参照先のValueを取得するためにElem()
を使用することで、値が設定可能になります。
var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println(v.Elem().CanSet()) // true
Set
型を意識せずに値を設定したいだけならSet()
を呼び出せば良い。
func a(rv reflect.Value, v interface{}) {
src := reflect.ValueOf(v)
rv.Elem().Set(src)
}
SetXXX
Set()
ではなく、型に対応したメソッドを使うという方法もある。
設定したい型に対応するメソッドが用意されているので実行すれば良い。
func a(dist reflect.Value, v interface{}) {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Bool:
dist.SetBool(rv.Bool())
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
dist.SetInt(rv.Int())
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
dist.SetUint(rv.Uint())
case reflect.Float32, reflect.Float64:
dist.SetFloat(rv.Float())
case reflect.String:
dist.SetString(rv.String())
default:
fmt.Printf("other(%s)", rv.Kind())
}
}
Arrayの各indexに値を設定
Index().Set()
を使う。
当然だがlen(dist)
< len(src)
の場合はpanic(reflect: array index out of range
)が発生するので
ちゃんとやるなら双方のLen()
のチェックを行うべきである。
func main() {
var s [3]string
rv := reflect.ValueOf(&s)
fmt.Println(s) // ["", "", ""]
a(rv, []string{"a", "b"})
fmt.Println(s) // ["a", "b", ""]
}
func a(rv reflect.Value, src []string) {
dist := rv.Elem()
for i, s := range src {
dist.Index(i).Set(reflect.ValueOf(s))
}
}
Sliceに値を追加
reflect.AppendSlice()
を使用し、戻り値をSet()
に渡す。
reflect.AppendSlice()
はSlice同士の結合を行うため、第2引数にはSliceをreflect.ValueOf()
した結果を渡す必要がある。
func main() {
var s []string
rv := reflect.ValueOf(&s)
fmt.Println(s) // []
a(rv, []string{"a", "b", "c"})
fmt.Println(s) // ["a", "b", "c"]
a(rv, []string{"1", "2", "3"})
fmt.Println(s) // ["a", "b", "c", "1", "2", "3"]
}
func a(rv reflect.Value, src []string) {
dist := rv.Elem()
dist.Set(reflect.AppendSlice(dist, reflect.ValueOf(src)))
}
Mapのキーを指定して値を設定
SetMapIndex()
にキーと値のValueを設定する。
func main() {
s := map[string]string{}
rv := reflect.ValueOf(&s)
fmt.Println(s) // map[]
a(rv, map[string]string{"a": "hoge", "b": "fuga"})
fmt.Println(s) // map[a:hoge b:fuga]
a(rv, map[string]string{"b": "mage", "c": "age"})
fmt.Println(s) // map[a:hoge b:mage c:age]
}
func a(rv reflect.Value, src map[string]string) {
dist := rv.Elem()
for key, val := range src {
dist.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(val))
}
}
値を初期化する
Arrayを作る
reflect.ArrayOf()
を使う。
例えば[5]string
を作りたい場合は以下のようになる。
reflect.New()
はreflect.Ptr
型のreflect.Value
を返すので、Elem()
を使用しする必要がある。
rv := reflect.New(reflect.ArrayOf(5, reflect.TypeOf("")))
fmt.Printf("%#v\n", rv) // &[5]string{"", "", "", "", ""}
fmt.Printf("%#v\n", rv.Elem()) // [5]string{"", "", "", "", ""}
Sliceを作る
reflect.MakeSlice()
を使う。
make()
と同様にlen, capを指定して使う。
s := []string{}
rv := reflect.MakeSlice(reflect.TypeOf(s), 5, 10)
fmt.Printf("%#v\n", rv) // []string{"", "", "", "", ""}
fmt.Printf("len: %d\n", rv.Len()) // len: 5
fmt.Printf("cap: %d\n", rv.Cap()) // cap: 10
}
Mapを作る
reflect.MakeMap
を使う。
s := map[string]string{}
rv := reflect.MakeMap(reflect.TypeOf(s))
fmt.Printf("%#v\n", rv) // map[string]string{}
構造体を作る
reflect.New()
に構造体のTypeを渡す。
s := struct {
A string
B int
}{}
rv := reflect.New(reflect.TypeOf(s))
fmt.Printf("%#v\n", rv) // &struct { A string; B int }{A:"", B:0}
fmt.Printf("%#v\n", rv.Elem()) // struct { A string; B int }{A:"", B:0}
}