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

huangapple go评论92阅读模式
英文:

How can I set the value of a struct variable on an interface slice?

问题

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

type testEntity struct {
	Val int
}

func main() {

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

	// 打印结果:can set false
	fmt.Println("can set", elemValue.Elem().Field(0).CanSet())

}

有人能解释为什么使用[]testEntity{}可以正常工作,而使用[]interface{}{}却不行吗?参考链接:http://play.golang.org/p/HW5tXEUTlP

英文:

How can I set testEntity.Val that is contained in []interface{}{} using reflection?

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

Can someone explain why it works with []testEntity{} but not []interface{}{} as shown here: http://play.golang.org/p/HW5tXEUTlP

答案1

得分: 8

根据这个问题,这种行为是正确的。

语义

原因是在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]的底层值的地址并保留类型,但这样的语法并不存在。反射也是如此。反射知道接口的底层类型,并且可以轻松地使用该信息来提供对slice[0]的底层值的类型安全指针,以便我们可以在其上使用Set(),但它没有这样做,因为Go没有提供这样的功能。

技术原因

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

如果你查看reflect/value.go中的Value.Elem()的定义,你会找到负责处理类型标志的那一行

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

在代码片段中,v是当前值,也就是你的elementValue。正如你所看到的,这将只复制只读标志,而不复制flagAddr,而后者是使用Value.Set()设置值所需的。将那一行改为:

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

将使我们能够使用Set(),但也会使得将interface{}值的底层值更改为任何其他值成为可能,从而破坏类型安全性。

英文:

According to this issue this behaviour is correct.

Semantics

The reason is that there is no equivalent syntax in Go that allows you to take the address of a value masked by an interface{} value.

This behaviour can be demonstrated in a simplified example. If you have the following code

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

then the line

slice[0].Val = 5

actually translates to

(&slice[0]).Val = 5

To modify a slice element it must be addressable, or else the value would not have been propagated back to the slice. Go does that automatically for you. Now let us modify the example and use an interface{} slice instead:

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

This example will obviously not work since interface{} does not have a field called Val. So, we would need to assert the type of slice[0]:

slice[0].(testEntity).Val = 5

While the type system is now content with that line, the addressability rules are not. Now the change of Val would be lost since we are operating on a copy of slice[0]. To counter this we would have to take the address of slice[0] and this, as you will see, leads us nowhere:

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

This code cannot work since (&slice[0]) is not of type interface{} anymore but *interface{}. As a consequence we cannot assert that value to *testEntity since only interface values can be asserted. This means that there is no Go syntax for setting an interface value in a slice and since reflection only models Go's behaviour, this will not work with reflection as well, even if it is technically possible.

We could imagine some syntax that takes the address of the underlying value of slice[0] while retaining the type but this syntax does not exist. The same goes for reflection. Reflection knows the underlying type of the interface and could easily use that information to provide a type-safe pointer to slice[0]'s underlying value so we can use Set() on that, but it does not since Go does not.

Technical reason

The reflect package uses several flags to mark the abilities of Value objects. The two flags related to setting values using Set are flagAddr for addressability and flagRO for marking Value objects as read-only (e.g. unexported attributes). To set using Set(), flagRO must be unset and flagAddr must be set.

If you look into the definition of Value.Elem() in reflect/value.go you will find the line responsible for handing the kind flags to the new value:

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

In the snipped v is the current value, elementValue in your case. As you can see, this will only copy the read-only flag and not the flagAddr that is needed for setting a value using Value.Set(). Changing that line to

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

will enable us to use Set() but will also make it possible to change the underlying value of a interface{} value to any other value, breaking type safety.

答案2

得分: 2

虽然Go语言不允许你合法地做你想做的事情,但你仍然可以使用unsafe包来实现。我将为你提供一个示例代码,以回答你如何实现以及为什么不能这样做的问题。

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

你可以在这个链接中查看代码示例:http://play.golang.org/p/vNdht-DMKg

英文:

While Go won't let you legally do what you want, you can still technically do it using unsafe. Just adding this to the discussion since you wanted to know how to do it and why you can't do it is answered already.

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

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

答案3

得分: 1

你需要使用指向结构体的指针,而不是值,你不能在副本上进行设置。

func main() {

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

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

}

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

英文:

You need to use a pointer to the struct, not a value, you can't set on a copy.

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())

}

huangapple
  • 本文由 发表于 2014年6月29日 07:42:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/24471907.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定