golang reflection tips
by Emile `iMil' Heitor - 2019-03-01
Because I’m the kind of person who likes genericity, I often find myself using features of languages that are flagged as “only use it if you know what you’re doing”. Golang reflection is one of those features, powerful yet a bit confusing.
Reflection, as explained in The Laws of Reflection is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming
In short, you can introspect variables at run-time, making your program exceptionally dynamic. How can this serve any purpose? well imagine for example creating function names dynamically by another function parameter. Pretty cool uh?
But first things first, let’s draw the scene, the reflect package exposes two principal functions that are used to dive into our variables, TypeOf
and ValueOf
. Those functions return, respectively, a reflect.Value
and reflect.Type
type.
Let’s start an example from there, consider a simple int
variable:
i := 3
Here’s a reflect
representation of i
’s type:
r := reflect.TypeOf(i)
Without any surprise, if we print it, we get int
:
fmt.Printf("%v\n", r)
Using a similar syntax, here we get the value of i
:
r := reflect.ValueOf(i)
Which prints i’s value
Actually, you can retrieve i
’s type using the Value.Type()
method:
fmt.Printf("%v\n", r.Type())
In the previous ValueOf
example we only wanted to print i
’s value, but it turns out we also can modify it, yet we need to make a little change to the call.
When we do ValueOf(i)
, we actually copy i
to the Value
part of the reflection, so naturally, changing it would not affect the initial value of i
. In fact, this would be so useless that it’s not even permitted
The error says that the value is unadressable, and this can be checked with the CanAddr
method.
What we need here looks like what we would do in a C
program where a function needs to modify a parameter: passing the address of the parameter, and instead of dereferencing it with a *
, we’ll use the Elem()
method:
fmt.Println(reflect.ValueOf(&i).Elem().CanAddr())
Now we can modify i
’s content using the SetInt()
method
Another nice trick with reflection is the ability to inspect a struct
content, either by index, by field name or even by tag if the struct
happens to have some.
Let’s first try a simple enumeration of a struct
fields:
var s struct {
name string
id int64
}
r := reflect.TypeOf(s)
for i := 0; i < r.NumField(); i++ {
fmt.Printf("%v\n", r.Field(i))
}
As you might have guessed, r.NumField()
returns the number of fields in the struct
, and r.Field(i)
accesses the i
th field of the struct
. This loop will print a StructField
, which contains many informations about the field, if you only want the field’s name, simply request r.Field(i).Name
.
Here’s another example using reflect
ability to inspect struct
tags:
var s struct {
name string `daname`
id int64 `daid`
}
r := reflect.TypeOf(s)
for i := 0; i < r.NumField(); i++ {
if r.Field(i).Tag == "daname" {
fmt.Printf("found tag for %v\n", r.Field(i).Name)
}
}
One last trick and possibly the coolest, or at least IMHO, reflection permits to build a function call 100% dynamically, only by reflecting variables. Why would one do that? Well imagine a program calling functions using os.Args
, without the need of a gigantic switch
of many if
.
Method building follows the same scheme as the Field
method, meaning that you can find and build a name either by index Method()
or by name MethodByName()
. There’s one catch though, don’t forget reflect
operates on variables, and we can’t search for a function from thin air, it has to be bound to some kind of variable; fortunately, Go gives the possibility to attach methods to variables. Here’s an example:
package main
import (
"fmt"
"reflect"
)
type RetVal int
func (r *RetVal) Foo() {
fmt.Println("hello from Foo!")
*r = 3
}
func main() {
var r RetVal
funcname := "Foo"
va := []reflect.Value{}
f := reflect.ValueOf(&r).MethodByName(funcname)
if f.IsValid() {
f.Call(va)
}
fmt.Printf("r is %d", r)
}
In order to “build” the function call, we’re created a custom type, RetVal
, to which we attach a Foo()
method.
As you can see, we declare a string
variable holding "Foo"
. It is mandatory to declare the build function call parameters list, and as we could expect, those are reflect.Value
types.
To be able to modify the value of r
, we pass it by reference, but we perfectly could have passed it by value if we were not to assign 3
to it in the function.
Before calling a dynamic function, it is always a good idea to verify that it is actually valid using the IsValid
method.
Finally, the created Value
(f
) can be Call()
ed with optional parameters.
I found this method useful when writing a Mattermost bot which features can be called through a parameter passed in the chat.