Reflect In Golang(1) Hacking The Great Confusion
Reflection is a kind of metaprogramming. With the help of reflection, the program can examine its own structure. In this article we first clarify how reflection work in Go, then build a file load program for action.
1. Types and Interface in Go
1.1 types in Go
Go is a statically typed lauguage, one variable only has one known type at any compile time. See the following example:
package main
type MyInt int
func main() {
var foo int = 10
var bar MyInt = foo
}
run this program:
./test_reflect.go:7:6: cannot use foo (type int) as type MyInt in assignment
Though the underline type of foo and bar is same(int), but their static type is different. They can not be assained to one another without conversion.
Let’s see the most important and great type in golang: Interface types. An Interface variable can store any concrete value as that value implements the interface’s methods.
// Reader is the interface that wraps the basic Read method.
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer is the interface that wraps the basic Write method.
type Writer interface {
Write(p []byte) (n int, err error)
}
Reader is an interface which has only one method: read. Anything that implements the method can be seen as a Reader, can be used as a Reader.
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
A variable of type io.Reader can hold any value whose type has a Read method. An extremely important example of an interface type is the empty interface:
interface{}
it is satisfied by all values at all. And Reflections and Interfaces are close related.
1.2 the representation of an interface
What is the internal data structure of an interface variable?
The interface variable contains a pair:
- the concrete variable(which implement the interface)
- the type info of the concrete variable(full information)
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
r is just pair (tty, *os.File), now we can do:
var w io.Writer
w = r.(io.Writer)
r.(io.Writer) is a type assertion;
var empty interface{}
empty = w
the empty interface variable contain all the type information of the concrete value, but it can not access any mothod of this value. (it is empty:))
2. The laws of reflection
2.1 interface value to reflect object:
Go’s reflect package has tool to examine the (value, type) pair stored in a interface variable.
- reflect.Type – get by reflect.TypeOf()
- reflect.Value – get by reflect.ValueOf()
var x float64 = 3.14
fmt.Println("type: ", reflect.TypeOf(x))
the program prints:
type: float64
The signature of reflect.Typeof:
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
When we call reflect.TypeOf(x), x is first stored in an empty interface, which is then passed as the argument; reflect.TypeOf unpacks that empty interface to recover the type information.
fmt.Println("value: ", reflect.ValueOf(x))
fmt.Println("value: ", reflect.ValueOf(x).String())
result:
value: 3.14
value: <float64 Value>
other useful method of reflect.Type and reflect.Value:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
print:
type: float64
kind is float64: true
value: 3.4
2.2 reflection object to interface value
Given a reflect.Value we can recover an interface value using the Interface method; in effect the method packs the type and value information back into an interface representation and returns the result:
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
Interface() method is the reverse of ValueOf()!!
2.3 to modify a reflection object, the value must be settable
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
run the previous code, you will get:
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
The problem is not that the value 7.1 is not addressable; it’s that v is not settable. Settability is a property of a reflection Value, and not all reflection Values have it.
we can use tool test if a reflect.Value can be setted:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("v can be setted?: ", v.CanSet())
prints:
v can be setted?: false
We called Set method on a non-settable Value! What is settability?? Ans: When we use reflect.ValueOf(x) create a interface variable, the value it contains is not the real x, but a copy of x. So, your “Set” on this fake value have no effect on the real one, it is just a waste, so Go just refuse this kind of usage!
Just reflect on the address of the variable:
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("p's type: ", p.Type())
fmt.Println("p's kind: ", p.Kind())
fmt.Println("p can be setted?: ", p.CanSet())
print:
p's type: *float64
p's kind: ptr
p can be setted?: false
the reflect object v can’t be settable, not the value it contains:
v := p.Elem()
fmt.Println("v can be setted?: ", v.CanSet())
print:
v can be setted?: true
now we set it:
v.SetFloat(7.1)
fmt.Println(x)
print:
7.1
3. reflect and struct
As long as we can get the address of a structure, we can modify its fields.
type T struct {
A int
B string
}
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())
}
print:
0: A int = 23
1: B string = skidoo
Tips: The field name of T must upper case, then it can be settable.
s.Field[0].SetInt(77)
s.Field[0].SetString("Test")
fmt.Println("t is now", t)
print:
t is now {77 Test}
4. Reflection and Slice
Let's see a simple example, use reflection traverse two different type of slices.
package main
import (
"fmt"
"reflect"
)
func main() {
strArr := []string{"jim", "marry", "alon"}
testSliceReflect(strArr)
intArr := []int{1, 3, 2, 4}
testSliceReflect(intArr)
}
func testSliceReflect(t interface{}) {
switch reflect.TypeOf(t).Kind() {
case reflect.Slice:
s := reflect.ValueOf(t)
for i := 0; i < s.Len(); i++ {
fmt.Println(s.Index(i))
}
}
}
First, use reflect.TypeOf(t).Kind() get the kind of object contained in the interface value. If it is a slice, no matter the type of elements in the slice, just print it out.
Now, we begin to see a complicated example: pow a int slice
package main
import (
"fmt"
"reflect"
)
func main() {
foo := []int{1, 2, 3, 4, 5, 6}
testSliceReflectWrite(&foo)
fmt.Println(foo)
}
func intPow(i int) int {
return i * i
}
func testSliceReflectWrite(t interface{}) {
if reflect.TypeOf(t).Kind() != reflect.Ptr {
fmt.Println("parameter not a ptr")
}
s := reflect.ValueOf(t).Elem()
etype := reflect.TypeOf(s.Interface()).Elem()
tmp := reflect.New(etype).Elem()
for i := 0; i < s.Len(); i++ {
pow := intPow(int(s.Index(i).Int()))
tmp.Set(reflect.ValueOf(pow))
s.Index(i).Set(tmp)
}
}
We pass a pointer to slice to the testSliceReflectWrite, so we can assign value to the element of the slice.
First, reflect.ValueOf(t) get the value of t:
fmt.Printf("ps: %v\n", reflect.ValueOf(t))
print:
ps: &[1 2 3 4 5 6]
the value in the interface variable t is a slice pointer
Next, reflect.ValueOf(s).Elem() get the value the pointer point to. Yes, it is the slice.
fmt.Printf("s: %v\n", s)
print:
s: [1 2 3 4 5 6]
Now, we want get the the type of the slice element, so we can creat a temp variable according to this standard type. Because we have the reflect.Value type of s, we use Interface() get back the type info, then TypeOf() get type info.
fmt.Println(reflect.TypeOf(s.Interface()))
print:
[]int
Yes, it is a int slice, Elem() on this reflect.Type:
etype := reflect.TypeOf(s.Interface()).Elem()
fmt.Println(etype)
print:
int
reflect.New(etype) return a ptr point to value of this type(int) so Elem() get the value; finally the for loop do the pow setting. run the whole program:
before: [1 2 3 4 5 6]
after: [1 4 9 16 25 36]
Just do a slice pow, it's an overkilling to use reflect. But this is a good example tell us how we can use reflect to get and set our own data.