Go reflections deep dive— from structs and interfaces
Go reflections is a topic that requires understanding of the go internals regarding structs, interfaces and the type system in order to understand how it fundamentally works behind the scenes.
You can use reflections without going into these details, of course. This article objective is to introduce you to some of the details in a way that will enable deeper understanding. But those are not strictly required.
This article assumes you have basic understanding of structs and interfaces.
You may have a quick refresh in “Go by example” for struct and interfaces, or go deeper into the Go tour for structs and interfaces.

What is reflection?
In computer science, reflection is the ability of a process to examine, introspect, and modify its own structure and behavior. — Wikipedia
Reflections are runtime manipulations of the program. It is a form of meta-programming, but not everything considered meta-programming is reflection.
Why does it matter for Go?
There are many aspects in which reflections matter. Through this guide I will focus on the most obvious one:
Go is a statically typed language, you have to declare all your types ahead of time. As such, you have no way of handling types you do not know ahead of time, even when the manipulation, examination or introspect you need to do with them does not require this knowledge ahead of time.
A good example is the print
method of fmt
. If you want to print the type of a variable using %T
, the fmt
package does not need to know about your own created struct
for example called Person
. But it still can print Person
out.
The empty interface
An interface is a type with set of methods, which every struct that implements these methods are automatically implementing the interface. This allows to use the interface as a type for methods, that you can pass in the structs that implements it in.
For an empty interface then, every struct, and every “primitive” type inherently implements it.
Therefore, using the empty interface as type allows to pass in any type in its place. Like using any
type, but not really.
The above code will work, and will print 101
for the first print, as it use +
on x which is typed int, and then passes to myPrint
, it sends it to Println
that also accepts empty interface values, and using reflections internally.
But Go is still a statically typed language, so using the empty interface will not allow you to do anything with the variable (unless you use type assertions or reflections).
The above code will not compile. within myPrint
the type of item
is empty interface, so even though the underlying type is integer, go does not “know” about it yet, and therefore will panic.
Type assertions
Type assertions are a utility that help you validate the underlying type for some variable, and in case it is the type you asserted for, get the underlying value variable recognized as this type.
This will print out the number 10, as we first assert the type using myVar.(int)
. Into v
is assigned the typed variable if the type assertion is successful, and into ok
is assigned a boolean value of whether the assertion was successful.
There is more to type assertions, if you are interested I recommend the Go tour for type assertions and type switches.
Where is the type details?
How does the type assertion (and by proxy, reflection too) do know of the underlying type of a more generic interface (empty interface or otherwise).
To understand that, we will take a look at the go
implementation directly, at the time of writing in go/src/sync/atomic/value.go . That implements the basic value of every variable in go.
// ifaceWords is interface{} internal representation. type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
Every empty interface, and by extension every value in go, has in its basic representation 2 unsafe pointers: typ
and data
.
typ
holds the type information for the current variable, so even though a variable might be an empty interface, the actual type information is intact within typ
.
data
holds the value itself, alongside more data such as kind
that is out of scope for this article. The important point is that data
holds the value *and* some more things, whereas typ
only holds the type data.
What is missing in type assertions? (Or: why do we need reflections?)
Type assertions allows for validation and usage of the underlying type for interfaces when you know what type to check for.
In the example above, we use assert for int
specifically. That means we knew ahead of time the type, for the type assertion to work. Even if we switch
and check for multiple options, we still have to know of the concrete type we assert in compile time.
In cases where we don’t know of the concrete types in compile time, reflections come in. Or in other words, as stated in the beginning of this article, when we need to do runtime examinations.
Think back to the fmt.Print
example, the fmt
package does not know about our structs types but can still print its value and type (using %T
).
Finally: Reflections
Reflect.Type and Reflect.Value
These are the two basic and most important types provided by the Reflect package. Reflect.Type
and Reflect.Value
are structs defined within the Reflect
package only, and the reflect package have methods operating on interface variables that internally populate these structs using the underlying typ
and data
available to every interface in go.
Reflect.TypeOf()
and Reflect.ValueOf()
are the 2 basic methods available, respectively returning Reflect.Type
and Reflect.Value
, as shown in this example:
It’s worth taking a look at the same example with our own created structs too, for more clarity:
Using TypeOf
and ValueOf
gives back to us the underlying type, and a pointer to the value. It’s essential to understand the value coming from ValueOf
is of type Reflect.Value
, and not the original variable type.
So for example, in the int example above, we can’t take the value and do arithmetic, e.g myValue + 1
with it. It will fail to compile, as Go compiler does not recognize this action for Reflect.Value
, but just for Integer types.
We’ll get into how you can use the value again, if needed.
Reflect.Kind
Understanding kinds might be tricky, and resources you might find online can be confusing as most of them handle kind system from type theory and talks about implementation in languages like Haskell.
What you need to know in regards to Go, is that every variable has a kind, derived from its type. Kinds are like the types of types.
It’s easiest to demonstrate with places where you create your own types: Structs.
Let’s go back to the code above where we create the Person
struct. The person we creates has a type Person
. The type of the Person
type, namely, its Kind
, is Struct
.
Getting a type kind is done by applying the Kind()
method on a Reflect.Type
variable.
You can see the full list of kinds available here: https://golang.org/pkg/reflect/#Kind
For some instances, other than the obvious example of structs, kinds looks like duplication — e.g type int64 has kind of int64. Don’t stress it, for our purpose here knowing the kind is helpful with reutilizing the empty interface value (as Reflect.Value
is not usable by itself, demonstrated in the section above).
Converting Reflect.Value
back to original type value
So we have a value with type Reflect.Value
after we analyzed an empty interface variable passed to us using Reflect.ValueOf()
. But this value is not really usable, as Go type system does not recognize it as its original type, yet.
We want to convert the value itself to the original type. The process for that will be:
- Figure out the exact original type. Use
Reflect.TypeOf()
orReflect.Kind()
as needed. - Get the raw value data using pointer to the value. (Unsafe pointer that is, but this is out of scope for now)
- Type cast the pointer.
Lucky for us, the reflect
package already handles that for us for all basic types.
The basic types conversion methods
reflect
package provides method on reflect.Value
that convert the value back to a typed original value. Lets see it in action with an integer:
There are conversion methods available for all basic types, Bool
, Float
, String
etc’ etc’.
Complex types investigation
The above covers basic types, but what do we do with Structs for example?
The reflect
package provides methods to investigate structs “from the outside” as well. Receiving how many fields there are and receiving individual fields.
A code example will make it clear:
Thank you!
Thank you for putting the time into reading this article, I sincerely hope it was useful (:
You can follow me on twitter, I write about all different technologies: https://twitter.com/snird