Goの勉強をやり始めたのでメモ。
「プログラミング言語 Go」を読んでる。
- 不明な型の変数に対して、その変数の情報を調べたり操作したりする機能をリフレクションと呼ぶ。
なぜリフレクションなのか
- 不明な型の変数を取り扱いたケースはある。
- 例えば
fmt.printf()
の第2引数以降には多くの型を渡せる。
- 例えば
- 型switchを使えば一見できそうだが、あまり現実的ではない。
- すべての型のケースを書く?
- すべてのパッケージに依存してしまう。
- 基底型と派生型は一致しない。
- ここでリフレクションが必要になる。
reflect.Type と reflect.Value
- reflectパッケージには
reflect.Type
とreflect.Value
という型がある。 reflect.Type
はその変数の動的な型を表す。reflect.TypeOf(i interface{})
を使用して取得する。interface{}
への暗黙的な変換をすると、変数は動的な型と動的な値を持つ。- 動的な型は基本的に具象型を指す。
- よって、
io.Writer
を渡してもreflect.TypeOf()
で取得できるのは*os.File
などである。
printf
では%T
を使用するとその変数の動的な型を出力する。
reflect.Value
はその変数の動的な値を表す。reflect.ValueOf(i interface{})
を使用して取得する。reflect.Value.String()
はその値を返さないので注意すること。- 値がほしい場合は
printf
の%v
を使用する。
- 値がほしい場合は
reflect.Value.Interface()
を使用することでinterface{}
に変換できる。reflect.Type
で表現される型は無限にあるが、reflect.Kind(i interface{})
で取得できる型の種類は有限である。
reflect.Valueでの変数の設定
- Goの式の中には変数を示すものもあれば、変数を表さないものもある。
x
,x.f[1]
,*p
は変数を示す。x+1
,f(2)
は変数を示さない。
- 変数は値を含むアドレス化可能なメモリ位置であり、その値はアドレスを通じて変更できる。
reflect.Value
も同様にアドレス化可能なものやそうではないものがある。reflect.Value(x)
やreflect.Value(&x)
はアドレス化可能ではない。reflect.Value(&x).Elem()
はアドレス化可能である。
- どのような変数xにでも、
reflect.Value(&x).Elem()
を呼び出すことでアドレス化可能な Valueを得られる。 - ポインタを通してアクセスした場合にはアドレス化可能な Valueを得られる。
- あるアドレス化可能ではないスライス e に対して、
e
はアドレス化可能ではないが、e[i]
はアドレス化可能である。 - あるアドレス化可能ではないスライス e に対して、
reflect.Value(e)
はアドレス化可能ではないが、reflect.Value(e).Index(i)
はアドレス化可能である。
- あるアドレス化可能ではないスライス e に対して、
- 以下のようにすることでリフレクションを通じて変数の値を書き換えることができる。
x := 2 d := reflect.ValueOf(&x).Elem() // アドレス化可能な式 px := d.Addr().Interface().(*int) // アドレス化可能な式のアドレス(ポインタ) *px = 3 fmt.Println(x) // 3
- これを
reflect.Value.Set()
を使用すると簡単に書ける。
d.Set(reflect.ValueOf(3))
- アドレス化可能ではない
reflect.Value
や、設定しようとする値の型が違う場合はパニックになる。 SetInt()
などの基本型に特化したSetもある。- 基底型が等しければ成功する。
- 値が大きすぎる場合は切り詰められる。
interface{}
型への設定は失敗する。
var x interface{} rx := reflect.ValueOf(&x).Elem() rx.SetInt(2) // panic rx.Set(reflect.ValueOf(3)) // Setの場合はOK
- パッケージ非公開のフィールドは取得できるが更新はできない。
Set()
が使用可能かを調べるには、アドレス化可能であるかを調べるCanAddr()
ではなくCanSet()
を使うべきである。
構造体のフィールドタグへのアクセス
以下のようなコードでタグにアクセスできる。
// ptr はタグの付いた構造体 v := reflect.ValueOf(ptr).Elem()for i := 0; i < v.NumField(); i++ { fieldInfo := v.Type().Field(i) tag := fieldInfo.Tag name := tag.Get("http") }
型のメソッドを表示する
relect.Type
やreflect.Value
はMethod
というメソッドを持っている。t.Method(i)
はreflect.Method
を返し、v.Method(i)
はreflect.Value
を返す。reflect.Value.Call
によって呼び出すことができる。
注意書き
- リフレクションは注意して使うべきである。
- リフレクションに基づくコードは脆弱になりえる。
- 基本的にコンパイラでミスを発見できず、実行時にpanicになる。
- ドキュメンテーションとしての型がないため、可読性が下がる
- 実行速度が遅い
- リフレクションに基づくコードは脆弱になりえる。