Получить указатель на значение, используя отражение

У меня есть функция, которая перебирает все поля интерфейса, передаваемого в качестве параметра. Для этого я использую рефлексию. Проблема в том, что я не знаю, как получить адрес поля без указателя. Вот пример:

type Z struct {
    Id int
}

type V struct {
    Id int
    F Z
}

type T struct {
    Id int
    F V
}

Приведенный выше код представляет мои тестовые структуры. Теперь вот фактическая функция, которая пересекает указанную структуру и перечисляет подробности о ней:

func InspectStruct(o interface{}) {
     val := reflect.ValueOf(o)
     if val.Kind() == reflect.Interface && !val.IsNil() {
        elm := val.Elem()
        if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
            val = elm
        }
     }
     if val.Kind() == reflect.Ptr {
        val = val.Elem()
     }

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        address := "not-addressable"

        if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
            elm := valueField.Elem()
            if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
                valueField = elm
            }
        }
        if valueField.Kind() == reflect.Ptr {
            valueField = valueField.Elem()
        }
        if valueField.CanAddr() {
            address = fmt.Sprint(valueField.Addr().Pointer())
        }

        fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name, 
            valueField.Interface(), address, typeField.Type, valueField.Kind())

        if valueField.Kind() == reflect.Struct {
            InspectStruct(valueField.Interface())
        }
    }
}

А вот актуальный тест после создания / инициализации структуры:

t := new(T)
t.Id = 1
t.F = *new(V)
t.F.Id = 2
t.F.F = *new(Z)
t.F.F.Id = 3

InspectStruct(t)

И, наконец, вывод вызова InspectStruct:

Field Name: Id,  Field Value: 1,     Address: 408125440 , Field type: int   , Field kind: int
Field Name: F,   Field Value: {2 {3}},   Address: 408125444 , Field type: main.V    , Field kind: struct
Field Name: Id,  Field Value: 2,     Address: not-addressable   , Field type: int   , Field kind: int
Field Name: F,   Field Value: {3},   Address: not-addressable   , Field type: main.Z    , Field kind: struct
Field Name: Id,  Field Value: 3,     Address: not-addressable   , Field type: int   , Field kind: int

Как вы можете видеть, я использую рекурсию, поэтому, если одно из полей является структурным, я вызываю InspectStruct для него. Моя проблема в том, что, хотя все поля были инициализированы для всей структуры "t" иерархии, я не могу получить адрес для любого поля, расположенного на большей глубине, чем "t". Я был бы очень признателен за любую помощь.

5 ответов

Решение

Переходя reflect.Value вместо interface{} кажется, чтобы решить проблему, однако я не знаю, почему valueField.Interface() не работает

Рабочий пример: http://play.golang.org/p/nleA2YWMj8

func InspectStructV(val reflect.Value) {
    if val.Kind() == reflect.Interface && !val.IsNil() {
        elm := val.Elem()
        if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
            val = elm
        }
    }
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        address := "not-addressable"

        if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
            elm := valueField.Elem()
            if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
                valueField = elm
            }
        }

        if valueField.Kind() == reflect.Ptr {
            valueField = valueField.Elem()

        }
        if valueField.CanAddr() {
            address = fmt.Sprintf("0x%X", valueField.Addr().Pointer())
        }

        fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
            valueField.Interface(), address, typeField.Type, valueField.Kind())

        if valueField.Kind() == reflect.Struct {
            InspectStructV(valueField)
        }
    }
}

func InspectStruct(v interface{}) {
    InspectStructV(reflect.ValueOf(v))
}

Я собирался оставить это как комментарий к сообщению OneOfOne, но оно оказалось слишком сложным для комментария и очень уместно для ответа на вопрос и почему ответ OneOfOne работает правильно.

Причина Interface() не работает из-за интерфейсной оболочки, которую он возвращает. Чтобы дать представление о том, что происходит, давайте посмотрим на то, что мы делаем, не задумываясь:

type MyStruct struct {
    F Foo
}

type Foo struct {
    i int
}

func ExtractField(ptr *MyStruct) interface{} {
    return ptr.F
}

func main() {
    ms := &MyStruct{Foo{5}}
    f := ExtractField(ms).(Foo) // extract value
    f.i = 19
    fmt.Println(f, ms.F)            // ???
    fmt.Println(&f == &ms.F)        // Not the same!
}

( Детская площадка)

Однако подумайте о interface{} это возвращается. Что это упаковка? Значение ptr.F - то есть копия этого. Это то, что value.Interface делает, он возвращает вам interface{} обертывание поля. Больше нет метаданных указателя, они полностью отделены от исходной структуры.

Как вы заметите, передача значения непосредственно reflect.ValueOf всегда вернется false для CanAddr для "верхнего уровня" - потому что этот адрес не имеет смысла, так как он даст вам адрес копии значения, изменение его на самом деле ничего не будет значить. (Имейте в виду, что указатели тоже являются значениями - если вам нужен адрес поля со значением указателя, например *Fooвы действительно ищете **Foo).

Итак, в нашем примере выше, если бы мы были наивно передать в reflect.ValueOf(ExtractField(ms)) мы бы получили ValueOff, который не только не имеет нужного вам адреса - он даже не адресуемый в соответствии с отражением, поскольку он никогда не даст действительного адреса в том, что касается отражения (единственный адрес, который он может дать вам, - это адрес внутренняя копия значения в Value структура).

Так почему прохождение Value вниз по кроличьей норе работает? Ну, единственный реальный способ сказать, что это reflect.Value поддерживает необходимые метаданные при использовании Elem а также Field, в то время как interface{} не могу. Так что пока reflect.Value может выглядеть так:

// Disclaimer: not the real structure of a reflect.Value
type Value struct {
    fieldAddress uintptr
    value        Foo
}

Все, что он может дать вам, это

// Again, an abstraction of the real interface wrapper 
// just for illustration purposes
type interface{} struct {
    value Foo
}

Сегодня я спустился по кроличьей норе, многому научился, изучая этот код и ответ Джсора, спасибо. Я пришел к другому выводу относительно вашей проблемы, которая, возможно, привела к более простому решению:

1) Вы передаете указатель на структуру при первоначальном вызове функции, но...

2) Когда вы выполняете рекурсию, вызывая InspectStruct(valueField.Interface()), а не передавая встроенную структуру по указателю, вы передаете ее по значению.

Поскольку вы передаете по значению, go создаст временный файл и не позволит вам взять адрес. Вместо этого, когда вы делаете рекурсивный вызов, вызывайте valueField.Addr().Interface(), который будет передавать указатель на встроенную структуру.

    if valueField.Kind() == reflect.Struct {
-     InspectStruct(valueField.Interface())
+     InspectStruct(valueField.Addr().Interface())
    }

С этим изменением я получаю ожидаемый результат:

Field Name: Id,  Field Value: 1,     Address: 842350527552  , Field type: int   , Field kind: int
Field Name: F,   Field Value: {2 {3}},   Address: 842350527560  , Field type: lib.V , Field kind: struct
Field Name: Id,  Field Value: 2,     Address: 842350527560  , Field type: int   , Field kind: int
Field Name: F,   Field Value: {3},   Address: 842350527568  , Field type: lib.Z , Field kind: struct
Field Name: Id,  Field Value: 3,     Address: 842350527568  , Field type: int   , Field kind: int

Ответ @OneofOne идеален, но лучше добавить еще одну проверку

if valueField.IsValid() {
        fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name,
            valueField.Interface(), address, typeField.Type, valueField.Kind())
    }

это необходимо, потому что иногда вы можете запросить интерфейс из структуры с нулевым значением. Так бывает, паникует.

Некоторые из вышеперечисленных решений не работают, если элемент не адресуется. Я нашел такой способ:

      iface := valueField.Interface()
ptr := reflect.NewAt(fieldVal.Type(), unsafe.Pointer(&iface))
Другие вопросы по тегам