Skip to content

Notes of Lawys-Of-Reflection

This article notes ideas after reading go blog laws-of-reflection. This blog presents some whim ideas during reading and check further for them. It's not a duplication for the blog, and if you are not familiar with the topic, read the original blog first.

Reflection is a way to find out info behind an object at runtime. Such as getting all properties from a class dynamically, executing a method without actually having a reference to that methods object instance.

How Interface Holds The Value And Type

An interface variable can store any concrete (non-interface) value as long as that value implements the interface’s methods.

This is an important hint, which means if an interface variable is assigned by another interface variable, both of them stores the same underlying CONCRETE object.

func demo(){
    var a int = 3
    var i, i2 interface {}

    // inside interface i, type will be int and concrete value will be a
    i = a
    // inside interface i2, it saves no info about i
    // inside i2, it stores int and concerte value a as well
    i2 =  i
}

This way avoid a long dependency chains, like I1->I2->...->In->int variable. As the blog states, it CANNOT hold an interface type inside an interface.

One important detail is that the pair inside an interface variable always has the form (value, concrete type) and cannot have the form (value, interface type). Interfaces do not hold interface values.

Reflection

Reflection goes from interface value to reflection object

At the basic level, reflection is just a mechanism to examine the type and value pair stored inside an interface variable.

As go interface stores both type and value of a concrete type, reflect provides two types to represent it: reflect.Type and reflect.Value.

Reflection goes from reflection object to interface value.

  • An interface could be got from a reflect.Value
  • Package fmt unpack the reflect.ValueOf().Interface() for reflect.Value.

To Modify a Reflection Object, the Value Must Be Settable

Some reflect values are unsettable, as the reflect.Value simply duplicates a copy instead of a refernce of the original value inside an interface. Hence, some values are unsettable.

It’s the property that a reflection object can modify the actual storage that was used to create the reflection object. Settability is determined by whether the reflection object holds the original item.

Unsettable values are reasonable, as it's a kind of inconsistence to enable affecting the original value if the copied new value is modified.

That would be confusing and useless, so it is illegal, and settability is the property used to avoid this issue.

According to the settable rule, if a primitive value is going to be changed in reflect, a pointer(reference) should be passed while constructing reflect.Value. Correspondingly, there should be a syntax sugar to setup the value directly instead of assigning a pointer.

The reflect package feats method Elem to modify its value directly in such case. For examle:

var wantToChange int=3
// way1, use Elem to set the value
reflect.ValueOf(&wantToChange).Elem().Set(999)

// way2, assign a pointer
var newVal int=999
reflect.ValueOf(&wantToChange).Set(&newVal)

All of structs are copied in golang, if we want to modify it by reflection, pass the pointer!

What's more, only EXPORTED field is settable. Let's think about why unexported field is unsettable.

  • keep the behavior consistent, unexported fields are not intended to be awared of by outside users, as a result, we shouldn't allow reflect to modify it as well.
  • For a nice compatiable consideration, becuase of possible changes in unexported fields, callers shouldn't have any kind of dependencies attached to it.

However, it's still feasible to set up the private fields, with the help of unsafe package. However, it's far out of topic. The spring-go code example:

func PatchValue(v reflect.Value) reflect.Value {
    rv := reflect.ValueOf(&v)
    flag := rv.Elem().FieldByName("flag")
    ptrFlag := (*uintptr)(unsafe.Pointer(flag.UnsafeAddr()))
    *ptrFlag = *ptrFlag &^ flagRO
    return v
}