Подтвердить что ты не робот

Голанское отражение: невозможно установить поля интерфейса, обертывающие структуру

Я пытаюсь реализовать метод, который изменяет значение полей в объекте, который может иметь произвольную структуру. Трассировка полей не проблема, когда у меня есть указатель на структуру. Но я не могу изменить поля, когда у меня есть интерфейс, который не переносит указатель на структуру, а сама структура, короче:

// 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

4b9b3361

Ответ 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, а не самой структурой: это означает, что ссылочная структура может быть изменена.