Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

reflection

go

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

like image 402
Dan Avatar asked Jun 28 '14 23:06

Dan


3 Answers

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.

like image 170
nemo Avatar answered Oct 02 '22 14:10

nemo


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

like image 33
Daniel Williams Avatar answered Oct 02 '22 14:10

Daniel Williams


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

}
like image 20
OneOfOne Avatar answered Oct 02 '22 14:10

OneOfOne