Golang反射:无法设置包装结构体的接口字段

19
我正在尝试实现一种方法,可以更改具有任意结构的对象中字段的值。当我有指向结构体的指针时,遍历字段没有问题。但是当我拥有一个接口而不是指向结构体的指针时,我无法管理更改字段,简而言之:
// 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

我已经阅读了反射定律,因此我知道只有在拥有结构体指针时才能修改字段。所以我的问题是: 如何获得结构体数据的指针?

更新:

我使用基本的y := reflect.New(reflect.TypeOf(x))使其工作,因此现在可以设置y的值。有关详细示例,请参见:https://gist.github.com/hvoecking/10772475

1个回答

10

您似乎正在尝试修改接口变量中存储的动态值。您可以对接口变量执行的唯一操作是获取或设置动态值(进行复制的操作)以及检查存储值的类型。

要理解为什么会这样,请想象一下如果有这样的操作,我们将有以下代码:

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 指针,而不是结构体本身:这意味着可以修改所引用的结构体。


2
啊,我明白了。我的解决方法是(为了支持任意结构体)使用 y := reflect.New(reflect.TypeOf(x)) 创建一个新实例,并将 x 的所有字段复制到 y 的相应字段中。 - Heye
谢谢你,Heye,你又节省了我一个小时的搜索。 :) - Danyel
2
这很有道理,但为什么不能更改动态值内部的字段,例如“x.(list).head = z”——因为这不会创建悬空指针。 - WPWoodJr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接