The Relationship Between Interfaces and Reflection
Interfaces are one of the fundamental tools for abstraction in Go. Interfaces store type information when assigned a value. Reflection is a method of examining type and value information at runtime.
Go implements reflection with the reflect
package which provides types and
methods for inspecting portions of the interface structure and even modifying
values at runtime.
With this post I hope to illustrate how parts of the interface structure relate to the reflect API and ultimately make using the reflect package more approachable!
Assigning a Value to an Interface
An interface encodes three things: a value, a method set, and the type of the stored value.
The structure for an interface looks like the following:
We can clearly see the three parts of the interface in that diagram: the
_type
is type information, *data
is a pointer to the actual value, and the
itab
encodes the method set.
When a function accepts an interface as a parameter, passing a value to that function packs the value, method set, and type into the interface.
Examining Interface Data At Runtime with the Reflect Package
Once a value is stored in an interface, you can use the reflect
package to
examine its parts. We can’t examine the interface struct directly; instead the
reflect package maintains its own copies of the interface structure to which we
do have access.
Even though we’re accessing the interface via reflect objects, there’s a direct correlation to the underlying interface.
The reflect.Type
and reflect.Value
types provide methods to access
portions of the interface.
reflect.Type
focuses on exposing data about types and is therefore confined
to the _type
portion of the structure while reflect.Value
has to combine
type information with the value to allow programmers to examine and manipulate
values and therefore has to peek into the _type
as well as the data
.
reflect.Type – Examining Types
The reflect.TypeOf()
function is used to extract type information for a
value. Since its only parameter is an empty interface, the value passed to it
gets assigned to an interface and therefore the type, methodset, and value
become available.
reflect.TypeOf()
returns a reflect.Type
which has methods that allow you
to example the value’s type.
Below are a few of the Type
methods available and their corresponding bits of
the interface that they return.
An Example reflect.Type
Usage
|
|
The purpose of this program is to print the fields in our Gift
struct. When
the g
value is passed to reflect.TypeOf()
, g
is assigned to an interface
which the compiler populates with type and method set information. This allows
us to walk the []fields
of the type portion of the interface structure and we get
the following:
1 2 3 4 |
2018/12/16 12:00:00 Field 000: Sender string 2018/12/16 12:00:00 Field 001: Recipient string 2018/12/16 12:00:00 Field 002: Number uint 2018/12/16 12:00:00 Field 003: Contents string |
reflect.Method - Examining the itab/Method-Set
The reflect.Type
type also allows you to access portions of the itab
to
extract method information from the interface.
Examining Methods with Reflect
|
|
This code quite literally iterates over the function data stored in the itab
and displays the name of each method:
1 2 3 |
2018/12/16 12:00:00 Land 2018/12/16 12:00:00 TakeOff 2018/12/16 12:00:00 ToggleNose |
reflect.Value – Examining Values
So far we’ve only talked about type information – fields, methods, etc.
reflect.Value
gives us information about the actual value stored by an
interface.
Methods associated with reflect.Value
s necessarily combine type information
with the actual value. For example, in order to extract fields from a struct,
the reflect package has to combine knowledge of the layout of the struct –
particularly information about the fields and field offsets stored in the
_type
– with the actual value pointed to by the *data
portion of the
interface in order to properly decode the struct.
Example of Viewing an Modifying Values
|
|
1 2 3 4 |
2018/12/16 12:00:00 adults before nice: [{Bob Carpenter true} {Steve Clerk true} {Nikki Rad Tech false} {Hank Go Programmer false}] 2018/12/16 12:00:00 adults after nice: [{Bob Carpenter true} {Steve Clerk true} {Nikki Rad Tech false} {Hank Go Programmer true}] 2018/12/16 12:00:00 children before nice: [{Sue 1 true} {Ava 3 true} {Hank 6 false} {Nancy 5 true}] 2018/12/16 12:00:00 children after nice: [{Sue 1 true} {Ava 3 true} {Hank 6 true} {Nancy 5 true}] |
In this last example, we combine what we’ve learned to actually modify a value
via a reflect.Value
. In this case, someone wrote a function called nice()
(probably Hank) that will toggle any struct item in a slice from naughty to
nice where the name is “Hank”.
Notice that nice()
is able to modify the value of any slice you pass to it
and it doesn’t matter exactly what type it receives – as long as it is a slice
of a struct that has a Name
and Nice
field.
Conclusion
Reflection in Go is implemented using interfaces and the reflect
package.
There’s no magic to it – when you use reflection, you directly access parts of
an interface and values stored within.
In this way an interface almost behaves like a mirror, allowing a program to examine itself.
Though Go is a staticly-typed language, reflection and interfaces combine to provide extremely powerful techniques that are usually reserved for dynamic lanugages.
For more information about reflection in Go, definitely read the package documentation as well as the many other great blog posts on the subject.
About the Author
Yo! I’m Ayan and I hope you enjoyed my blog post! Part of my motivation for writing this is to learn more about Go myself. In that spirit, if you have anything comments or suggestions, please feel free to contact me at ayan@ayan.net or @ayangeorge on twitter.