Голанское отражение: невозможно установить поля интерфейса, обертывающие структуру
Я пытаюсь реализовать метод, который изменяет значение полей в объекте, который может иметь произвольную структуру. Трассировка полей не проблема, когда у меня есть указатель на структуру. Но я не могу изменить поля, когда у меня есть интерфейс, который не переносит указатель на структуру, а сама структура, короче:
// The following doesn't work
var x interface{} = A{Str: "Hello"}
// This panics: reflect: call of reflect.Value.Field on ptr Value
reflect.ValueOf(&x).Field(0).SetString("Bye")
// This panics: reflect: call of reflect.Value.Field on interface Value
reflect.ValueOf(&x).Elem().Field(0).SetString("Bye")
// This panics: reflect: reflect.Value.SetString using unaddressable value
reflect.ValueOf(&x).Elem().Elem().Field(0).SetString("Bye")
// This prints `false`. But I want this to be settable
fmt.Println(reflect.ValueOf(&x).Elem().Elem().Field(0).CanSet())
// This works
var z interface{} = &A{Str: "Hello"}
// This prints `true`
fmt.Println(reflect.ValueOf(z).Elem().Field(0).CanSet())
В длинном http://play.golang.org/p/OsnCPvOx8F
Я прочитал The Laws of Reflection, поэтому мне известно, что я могу изменять поля только тогда, когда у меня есть указатель на структуру. Итак, теперь мой вопрос: Как получить указатель на данные структуры?
UPDATE:
Я работал, используя в основном y := reflect.New(reflect.TypeOf(x))
, поэтому значения y
теперь можно настроить. Для подробного примера см. Это: https://gist.github.com/hvoecking/10772475
Ответы
Ответ 1
Кажется, вы пытаетесь изменить динамическое значение, хранящееся внутри переменной интерфейса. Единственными операциями, которые вы можете выполнять в переменной интерфейса, является получение или установка динамического значения (операции, выполняющие копии), и проверка типа хранимого значения.
Чтобы понять, почему все так, представьте, что была такая операция, и у нас был следующий код:
var ptr *A = pointer_to_dynamic_value(x)
x = B{...}
Что означает ptr
? Язык может повторно использовать хранилище при назначении новых значений интерфейсной переменной, поэтому ptr
теперь может указывать на память для значения B
, что нарушает безопасность типа языка (при этом текущее хранилище компиляторов только гарантированно будет использоваться повторно для небольших значений, но точка остается).
Единственным безопасным способом изменения значения, хранящегося в интерфейсе, является копирование значения, а затем назначение измененной версии. Например:
a := x.(A)
a.Str = "Bye"
x = a
Пакет reflect
отражает эти ограничения, поэтому reflect.Value
, представляющий поле динамического значения, считается только для чтения.
Вы можете установить поля в первом примере, потому что динамическое значение для z
является указателем *A
, а не самой структурой: это означает, что ссылочная структура может быть изменена.