如何在接口切片上设置struct变量的值?

时间:2014-06-28 23:42:39

标签: reflection go

如何使用反射设置testEntity.Val中包含的[]interface{}{}

type testEntity struct {
    Val int
}

func main() {

    slice := []interface{}{testEntity{Val: 3}}
    sliceValue := reflect.ValueOf(slice)
    elemValue := sliceValue.Index(0)

    // Prints: can set false
    fmt.Println("can set", elemValue.Elem().Field(0).CanSet())

}

http://play.golang.org/p/lxtmu9ydda

有人可以解释为什么它适用于[]testEntity{}而不是[]interface{}{},如下所示:http://play.golang.org/p/HW5tXEUTlP

3 个答案:

答案 0 :(得分:7)

根据this issue,这种行为是正确的。

语义

原因是Go中没有等效语法允许您获取被interface{}值掩盖的值的地址。

可以通过简化示例演示此行为。如果您有以下代码

slice := []testEntity{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 

然后是行

slice[0].Val = 5

实际上翻译为

(&slice[0]).Val = 5

要修改切片元素,它必须是可寻址的,否则该值不会传播回切片。 Go会自动为您完成。现在让我们修改示例并改为使用interface{}切片:

slice := []interface{}{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 

这个示例显然不起作用,因为interface{}没有名为Val的字段。因此,我们需要断言slice[0]

的类型
slice[0].(testEntity).Val = 5

虽然类型系统现在满足于该行,但可寻址规则不是。现在,Val的更改将丢失,因为我们正在运行slice[0]的副本。为了解决这个问题,我们必须采用slice[0]的地址,正如您将看到的那样,这将导致我们无处可去:

slice := []interface{}{testEntity{Val: 3}}
(&slice[0]).(*testEntity).Val = 5
fmt.Println(slice) 

此代码无效,因为(&slice[0])不再是interface{}类型,而是*interface{}。因此,我们无法将该值声明为*testEntity,因为只能声明接口值。这意味着没有用于在切片中设置接口值的Go语法,并且由于反射仅模拟Go的行为,因此即使在技术上可行,这也不适用于反射。

我们可以想象一些语法在保留类型的同时获取slice[0]的基础值的地址,但是这种语法不存在。反思也是如此。 Reflection 知道接口的基础类型,并且可以轻松地使用该信息来提供指向slice[0]基础值的类型安全指针,以便我们可以使用Set(),但它不会因为Go没有。

技术原因

反射包使用几个标记来标记Value个对象的能力。与使用Set设置值相关的两个标记是flagAddr的可寻址性,flagRO用于将Value个对象标记为只读(例如,未导出的属性)。要使用Set()设置,必须取消设置flagRO,并且必须设置flagAddr

如果你查看Value.Elem() in reflect/value.go的定义,你会发现line responsible用于将种类标志交给新值:

fl := v.flag & flagRO
// ...
return Value{typ, val, fl}

在剪切的v中,当前值为elementValue。如您所见,这只会复制使用flagAddr设置值所需的只读标志和而不是Value.Set() 。将该行更改为

fl := v.flag & (flagRO|flagAddr)

将使我们能够使用Set(),但也可以将interface{}值的基础值更改为任何其他值,从而打破类型安全。

答案 1 :(得分:2)

虽然Go不会让你合法地做你想做的事,但你仍然可以在技术上使用不安全的方法。只是将这个添加到讨论中,因为你想知道如何去做以及为什么你不能这样做已经得到了解答。

test := (*testEntity)(unsafe.Pointer(uintptr(unsafe.Pointer(&slice[0])) + 4))
test.Val = 5
fmt.Println(slice)

http://play.golang.org/p/vNdht-DMKg

答案 2 :(得分:1)

您需要使用指向结构的指针,而不是值,您无法在副本上设置。

http://play.golang.org/p/lXUglGDOKN

func main() {

    slice := []interface{}{&testEntity{Val: 3}}
    sliceValue := reflect.ValueOf(slice)
    elemValue := sliceValue.Index(0)

    // Prints: can set false
    fmt.Println("can set", elemValue.Elem().Elem().Field(0).CanSet())

}