Channels ▼
RSS

Design

A Brief Tour of the Go Standard Library


The Reflect Package

The reflect package provides runtime reflection (or introspection); that is, the ability to access and interact with values of arbitrary types at runtime.

The package also provides some useful utility functions such as reflect.DeepEqual(), which can compare any two values — for example, slices — which aren't comparable using the == and != operators.

Every value in Go has two attributes: its actual value and its type. The reflect. TypeOf() function can tell us the type of any value.

    x := 8.6
    y := float32(2.5)
    fmt.Printf("var x %v = %v\n", reflect.TypeOf(x), x)
    fmt.Printf("var y %v = %v\n", reflect.TypeOf(y), y)

Outputs

var x float64 = 8.6
var y float32 = 2.5

Here we have output two floating-point variables and their types as Go var declarations using reflection.

When the reflect.ValueOf() function is called on a value it returns a reflect.Value, which holds the value but isn't the value itself. If we want to access the held value, we must use one of the reflect.Value methods.

word := "Chameleon"
    value := reflect.ValueOf(word)
    text := value.String()
    fmt.Println(text)

Outputs

  Chameleon

The reflect.Value type has many methods for extracting the underlying type including reflect.Value.Bool(), reflect.Value.Complex(), reflect.Value.Float(), reflect.Value.Int(), and reflect.Value.String().

The reflect package can also work with collection types such as slices and maps, as well as with structs; it can even access structs' tag text. (This ability is used by the JSON and XML encoders and decoders.)

    type Contact struct {
	Name string "check:len(3,40)"
	Id int "check:range(1,999999)"
    }
    person := Contact{"Bjork", 0xDEEDED}
    personType := reflect.TypeOf(person)
    if nameField, ok := personType.FieldByName("Name"); ok {
	fmt.Printf("%q %q %q\n", nameField.Type, nameField.Name,
	    nameField.Tag)
    }

Outputs

    "string" "Name" "check:len(3,40)"

The underlying value held by a reflect.Value can be changed if it is "settable." The setability can be checked by calling reflect.Value.CanSet(), which returns a bool.

presidents := []string{"Obama", "Bushy", "Clinton"}
    sliceValue := reflect.ValueOf(presidents)
    value = sliceValue.Index(1)
    value.SetString("Bush")
    fmt.Println(presidents)

Outputs

 [Obama Bush Clinton]

Although Go strings are immutable, any given item in a []string can be replaced by another string, and this is what we have done here. (Naturally, in this particular example, the easiest way to perform the change would be to write presidents[1] = "Bush", and not use introspection at all.)

It is not possible to change immutable values themselves, but we can replace an immutable value with another value if we have the original value's address.

    count := 1
    if value = reflect.ValueOf(count); value.CanSet() {
	value.SetInt(2) // Would panic! Can't set an int.
    }
    fmt.Print(count, " ")
    value = reflect.ValueOf(&count)
    // Can't call SetInt() on value since value is a *int not an int
    pointee := value.Elem()
    pointee.SetInt(3) // OK. Can replace a pointed-to value.
    fmt.Println(count)

Outputs

  1 3

This snippet's output shows that the if condition's conditional evaluates to false, so its body isn't executed. Although we cannot set immutable values such as ints, float64s, or strings, we can use the reflect.Value.Elem() method to retrieve a reflect.Value through which we can set a pointed-to value, and this is what we do at the end of the snippet.

It is also possible to use reflection to call arbitrary functions and methods. Here is an example that calls a custom TitleCase() function (not shown) twice, once conventionally and once using reflection.

    caption := "greg egan's dark integers"
    title := TitleCase(caption)
    fmt.Println(title)
    titleFuncValue := reflect.ValueOf(TitleCase)
    values := titleFuncValue.Call(
	[]reflect.Value{reflect.ValueOf(caption)})
    title = values[0].String()
    fmt.Println(title)

Outputs

Greg Egan's Dark Integers
Greg Egan's Dark Integers

The reflect.Value.Call() method takes and returns a slice of type []reflect. Value. In this case, we pass in a single value (as a slice of length 1), and retrieve a single result value.

We can call methods similarly — and in fact, we can even query to see if a method exists and call it only if it does.

    a := list.New() // a.Len() == 0
    b := list.New()
    b.PushFront(1) // b.Len() == 1
    c := stack.Stack{}
    c.Push(0.5)
    c.Push(1.5) // c.Len() == 2
    d := map[string]int{"A": 1, "B": 2, "C": 3} // len(d) == 3
    e := "Four" // len(e) == 4
    f := []int{5, 0, 4, 1, 3} // len(f) == 5
    fmt.Println(Len(a), Len(b), Len(c), Len(d), Len(e), Len(f)) 

Outputs

 0 1 2 3 4 5

Here we create two lists (using the container/list package), one of which we add an item to. We also create a stack and add two items to it. And we create a map, a string, and a slice of ints, all of different lengths. We then use a Len() function to get their lengths.

    func Len(x interface{}) int {
	value := reflect.ValueOf(x)
	switch reflect.TypeOf(x).Kind() {
	case reflect.Array, reflect.Chan, reflect.Map,
	    reflect.Slice, reflect.String:
	    return value.Len()
	default:
	    if method := value.MethodByName("Len"); method.IsValid() {
		values := method.Call(nil)
		return int(values[0].Int())
	    }
	}
	panic(fmt.Sprintf("'%v' does not have a length", x))
    }

This function returns the length of the value it is passed or panics if the value's type isn't one that supports the notion of length.

We begin by getting the value as a reflect.Value because we will need this later on. Then we switch depending on the value's reflect.Kind. If the value's kind is one of the built-in types that supports the built-in len() function, we can call the reflect.Value.Len() function directly on the value. Otherwise, we have either a type that doesn't support the notion of length, or a type that has a Len() method. We use the reflect.Value.MethodByName() method to retrieve the method — or to retrieve an invalid reflect.Value. If the method is valid, we call it.

There are no arguments to pass in this case because conventional Len() methods take no arguments. When we retrieve a method using the reflect.Value.MethodByName() method, the returned reflect.Value holds both the method and the value. So, when we call reflect.Value.Call(), the value is passed as the receiver.

The reflect.Value.Int() method returns an int64; we have converted this to a plain int to match the generic Len() function's return value's type.

If a value is passed in that doesn't support the built-in len() function and doesn't have a Len() method, the generic Len() function panics. We could have handled this error case in other ways — for example, by returning -1 to signify "no length available," or by returning an int and an error.

Go's reflect package is incredibly flexible and allows us to do things at runtime that depend on the program's dynamic state. However, to quote Rob Pike, reflection is "a powerful tool that should be used with care and avoided unless strictly necessary." (Rob Pike has written an interesting and useful blog entry on Go reflection.)

Conclusion

This article brings to an end the set of five weekly tutorials on Go. By this point, you should have a good feel for the language, its tools, and its libraries — even how to run it on the Google App Engine to host Web apps. I hope you'll agree that Go is an interesting and enjoyable way of writing portable, native code.


Mark Summerfield is an independent trainer, consultant, and writer specializing in Go, Python, C++, and Qt. His most recent book is Programming in Go. This article is derived from information in Chapter 9 of that book.

Related Reading

Go Tutorial: Object Orientation and Go's Special Data Types

Getting Going with Go

RESTful Web Service in Go Powered by the Google App Engine

Why Not Go?


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video