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
According to this issue this behaviour is correct.
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.
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.
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
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())
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With