ちりもつもればミルキーウェイ

好奇心に可処分時間が奪われる

Goのreflectでこんなときどうする?集

はじめに

reflectでゴニョゴニョしたいときに毎回沼にハマってすごく時間をつかうので、使うパターンをメモっておく。

随時更新(したい)です

それではレッツゴー

任意の値があるプリミティブ型か確認したい

if reflect.ValueOf(v).Kind() == reflect.Int64 {
    // v kind is int64
}

任意の値がある具象型か確認したい

単に v.(SomeStruct) でもできるし v.(type) で型switchしてもできる。基本はそっちを使うほうが無駄なコストがかからずよい。

何らかの都合でreflect.Valueやreflect.Typeしか知らないなら以下のようにできる

if reflect.TypeOf(v) == reflect.TypeOf(SomeStruct{}) {
    // v type is SomeStruct
}

任意の値がポインタだったらそのポインタが示す値を取得する

rv := reflect.Elem()

任意のstructのフィールドのタグを取得

rt := reflect.TypeOf(v)
size := rt.NumField()
for i := 0; i < size; i++ {
    tag := rt.Field(i).f.Tag.Get("tag_name")
}

structの任意のフィールドがポインタでnilだったら、その型の空の値で埋めたい

rv := reflect.ValueOf(v).Elem().Field(fieldNum)
if rv.Kind() == reflect.Ptr && rv.IsNil() {
    rv.Set(reflect.New(rv.Type().Elem()))
}

ある型の空値のポインタを作りたい

ptr := reflect.New(reflect.TypeOf(v)).Interface()

型不明のsliceに型不明の要素をappendしたい

入力もreflect.Valueであり、かつappendしたい要素もreflect.Valueだ!ゴリゴリやらねば!みたいなときはしたいはず。型が一致してないとpanicします

sliceはあるslice型のreflect.Valueで、elemはそれに一致する型で値を作って値を埋めて突っ込むことを想定

slice := reflect.ValueOf(v)
elem := reflect.New(rv.Type().Elem()).Elem()
bindElem(elem, data)
rv = reflect.Append(rv, elem)

ある型がinterfaceを実装しているか確認

var iface someInterface
rt := reflect.TypeOf(v)
if rt.Implements(reflect.TypeOf(&iface).Elem()) {
    // v implements someInterface
}

あるinterfaceの実装をすべて列挙したい

非公開な reflect.typelinks() をつかえばできる。 //go:linkname ディレクティブを使って非公開関数をよぶぞ!

これが一番闇っぽい

//go:linkname typelinks reflect.typelinks
func typelinks() ([]unsafe.Pointer, [][]int32)

//go:linkname rtypeOff reflect.rtypeOff
func rtypeOff(unsafe.Pointer, int32) unsafe.Pointer

func getImplements(iface reflect.Type) ([]reflect.Value, error) {
    sections, offsets := typelinks()
    if len(sections) != 1 {
        return nil, fmt.Errorf("failed to get sections")
    }
    if len(offsets) != 1 {
        return nil, fmt.Errorf("failed to get offsets")
    }
    implements := make([]reflect.Value, 0)
    for i, base := range sections {
        for _, offset := range offsets[i] {
            typeAddr := rtypeOff(base, offset)
            typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
            if typ.Implements(iface) {
                implements = append(implements, reflect.New(typ.Elem()))
            }
        }
    }
    return implements, nil
}