Jan 19, 2019

[Go] reflection quick note

Reference:
https://blog.golang.org/laws-of-reflection

Retrospection is time consuming thus taking a note :-P


As with languages, reflection is based on type system.
Sadly, nowadays, the most strong typed language C++ does not have
a standard reflection system.

------


In Go, even the type refer to same underlying type, can not be assigned to
each other without a conversion.

C++
using INT = int;

INT i1 = 42;
int i2 = i1;

Golang
package main
type INT int

func main() {
 var i1 INT = 42
 var i2 int = int(i1)
 _ = i2
}


Understanding how Golang's interface type being implemented is essential to understand the Golang's reflection system.
i.e anonymous interface type it's type is always:
interface {}
It's storing value's type at run-time varies, and it's type is always interface{}

(Figure 1)


(Figure 2)


Tips

In Golang, when doing type inspection, if conversion failed, the left hand side type is default valued:

// if !ok, v is int's default value, which is 0
v, ok := any.(int)

// if !ok, v is interface_1's default value, which is nil
v, ok := any.(interface_1)
and this failed at run-time if conversion failed:
// run-time error: panic: interface conversion: interface {} is int64, not int
v := any.(int)

// run-time error: panic: interface conversion: interface {} is interface_2,
// not interface_1, which lack member functions.
v := any.(interface_1) 


When assigning/converting interface variable to interface variable, it's internal value has been assigned, the interface related function's index(ptr to member functions) has been assigned.




Law of reflection

1. Reflection goes from interface value to reflection object. (Always)

Reflection is a mechanism to examine the type and value pair stored inside an interface variable.

2 types to access interface's value/type pair:

2 functions to get the Type and Value type instance from an interface type instance:

  • getter and setter operate's on the largest value type of the same set of types.
    e.g.
    int -> int64

    Thus to get the real type's value, we have to shrink and converted to the value's real type (i.e it's Kind).
    e.g.
    // v.Uint() returns the largest value type: uint64
    // and the real type is uint8 thus we shrink converted back to uint8
    x = uint8(v.Uint())
  • .Type() give the value's type
  • .Kind() give type it stores: Uint, Float64, Slice, etc.
    For Go 1.14 we have 26 types.
    This describes the underlying type, not the alias type name.
    e.g.
    //Kind() returns int kind
    type INT int
  • .Float() retrieve it's value.
  • .Int()  retrieve it's value.
  • ...
  • .String() repr
    
    .Kind() gives the type it stores: Uint, Float64, Slice, etc.
    .String() repr



2. Reflection goes from reflection object to interface value.


  • .Interface() interface{}
    Packs the value back into interface{} and returns.
    e.g
    // y will have type float64.
    y := v.Interface().(float64)

    // fmt.Print... unpack interface internal value
    fmt.Println(v.Interface())



3. To modify a reflection object, the value must be settable.

e.g.
var x float64 = 3.4

// the interface{} has a copy of value x.
v := reflect.ValueOf(x)

// Error: will panic.  It's not setting the x, but the copy of it.
v.SetFloat(7.1)


reflect.Value:
  • .CanSet() // bool, can the underlying value being set?
  • .Elem() Return's the value (in Value type) of the *p points to.

e.g.
var x float64 = 3.4

// Note: take the address of x.
p := reflect.ValueOf(&x)

// *float64
fmt.Println("type of p:", p.Type())

// false?! Why? Because we do not want to set p, but *p
fmt.Println("settability of p:", p.CanSet())

Thus, we need .Elem() to dereference *p, i.e .Elem() dereference the *p, which is the address of x in the above example(Refer to Figure 1) :
v := p.Elem()

// true
fmt.Println("settability of v:", v.CanSet())
Same in C/C++, as long as we have the struct instance's address, we can modify it's fields.
e.g.
type T struct {
    A int        // Must be exported, thus upper case.
    B string    // Must be exported, thus upper case.
}

t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()

typeOfT := s.Type()

for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}
out:
0: A int = 23
1: B string = skidoo

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.