Thursday, May 20, 2010

Go language reflection examples

I have played around a bit with the new Go language (go, golang, go-lang). I couldn't find any good examples of how the reflection library worked so I made some brief examples below summarizing my findings.

Functions with variable number of arguments


First, it is possible to do many things without the reflect-package. For example, the following code shows how to distinguish between different types in a function using a variable number of parameters. The function split_into() splits a string into fields and put them all into different variables. The type of a variable determines how a field is converted (maybe a beginning of the c-language scanf).

// Split line at ',' and put into variables if
// number of fields match number of arguments.
// Handles string, int, and float.

func split_into(line string, a ...interface{}) bool {
    fa := strings.Split(line, ",", -1)
    if len(fa) == len(a) {
        for i, field := range a {
            switch f := field.(type) {
            case *string:
                *f = fa[i]
            case *int:
                *f, _ = strconv.Atoi(fa[i])
            case *float:
                *f, _ = strconv.Atof(fa[i])
            }
        }
        return true
    }
    return false
}

func testsplit() {
    var a int
    var b string
    var c float
    ok := split_into("12,This is some text,3.14", &a, &b, &c)
    fmt.Println(ok, a, b, c)
}
// testsplit() prints: "true 12 This is some text 3.14"

The example above shows how to unpack a variable number of arguments to a function and how to use a the special type switch statement. The declaration a ...interface{} is used instead of the simpler a ... to get all the arguments as a vector which seems to be more convenient than just "..." which gives a struct (a struct would require the reflect package I believe). The type of variable a inside the function is []interface{}, that is, a vector containing unknown objects.

Reflection example


For a reflection example, I tried to create a struct and then using reflection to modify its members. Due to a silly mistake my first attempt (below) failed:

type My_struct struct {
    I int
    S string
}

func reflect_test_1() {
    // First attempt. Didn't work:
    a := My_struct{1, "alpha"}
    aValue := reflect.NewValue(a).(*reflect.StructValue)
    fmt.Println(reflect.Typeof(aValue).String()) // Convenient way to check type
    i := aValue.Field(0).(*reflect.IntValue)
    i.Set(2)
    s := aValue.Field(1).(*reflect.StringValue)
    s.Set("beta")
    fmt.Println("I =", i.Get(), ", S =", s.Get())
    fmt.Println("I =", a.I, ", S =", a.S)
}
// Prints:
// *reflect.StructValue
// I = 2 , S = beta
// I = 1 , S = alpha

In the example above, the function reflect.NewValue() is first called to create a Value object. The Value object is used by the reflect package as an interface to the real data. It mainly contains a pointer to the actual data stored in the struct a. Since the actual data is a struct, NewValue() automatically creates a StructValue to refer to the data. The type assertion .(*reflect.StructValue) makes sure that aValue is a StructValue. Using the function Field() on our StructValue it is possible to refer to the fields of the struct and then Set() and Get() field values.

But, what was my problem with the example above? The final content in a is untouched. Clearly, when the fields are set to different values, the new values end up in a copy of the original struct, not in the original struct. The change below fixes the problem:

func reflect_test_2() {
    // Second attempt, success!
    a := My_struct{1, "alpha"}
    pValue := reflect.NewValue(&a).(*reflect.PtrValue)
    fmt.Println(reflect.Typeof(pValue).String())
    aValue = reflect.Indirect(pValue).(*reflect.StructValue)
    i := aValue.Field(0).(*reflect.IntValue)
    i.Set(2)
    s := aValue.Field(1).(*reflect.StringValue)
    s.Set("beta")
    fmt.Println("I =", i.Get(), ", S =", s.Get())
    fmt.Println("I =", a.I, ", S =", a.S)
}
// Prints:
// *reflect.PtrValue
// I = 2 , S = beta
// I = 2 , S = beta

The mistake in the first example was to pass the array a by-value instead of by-reference. Without the &-sign, a copy of the struct is created when NewValue() is called. We must pass a pointer to avoid the copying. However, the reflection gets a bit more complex since we need to traverse the pointer first before we find the struct.

A final note. If we make the fields in the struct private (non-exported) it will not work. The Set() operation seems to work only on exported fields:

type My_struct struct {
    i int
    s string
}
// reflect_test_1() or reflect_test_2() will now cause:
// panic: cannot set value obtained via unexported struct field

2 comments:

Kristian said...

The last parameter of split should be -1, to return all substrings:

fa := strings.Split(line,",", -1)

Thomas said...

I've updated the first example program to use -1 instead. The way to call Split was apparently changed 2010-07-01. So, if you use earlier versions of Go you should change back to 0...