Understanding the Go Runtime: The Reflect Package

valyala1 pts0 comments

The Reflect Package | Internals for InternsπŸ“š<br>Understanding the Go Runtime (10 of 10)<br>β–Ό1.<br>The Bootstrap<br>2.<br>The Memory Allocator<br>3.<br>The Scheduler<br>4.<br>The Garbage Collector<br>5.<br>The System Monitor<br>6.<br>The Network Poller<br>7.<br>Slices, Maps, and Channels<br>8.<br>The select Statement<br>9.<br>Stacktraces<br>10.<br>The Reflect Package<br>You are here

In the previous article<br>we watched the runtime rebuild an entire stack trace out of metadata the compiler and linker had frozen into the binary at build time. I told you at the end that reflect works on exactly the same trick β€” metadata baked into the binary, only pointed at your data instead of your call stack. Today we&rsquo;re going to cash that promise in.<br>Let&rsquo;s start with a program that, the first time you see it, feels like it shouldn&rsquo;t be possible:<br>package main

import (<br>"fmt"<br>"reflect"

type User struct {<br>Name string `json:"name"`<br>Email string `json:"email,omitempty"`<br>age int

func main() {<br>t := reflect.TypeOf(User{})<br>for i := 0; i t.NumField(); i++ {<br>f := t.Field(i)<br>fmt.Printf("%-6s %-8s %q\n", f.Name, f.Type, f.Tag)

Run it and out comes:<br>Name string "json:\"name\""<br>Email string "json:\"email,omitempty\""<br>age int ""<br>Field names. Field types. Even the struct tags, character for character. At runtime.<br>Now sit with how strange that is. Go is statically typed and compiled to native machine code . There&rsquo;s no virtual machine keeping class objects around, no interpreter that still has your source code in hand. By the time this program runs, User was supposed to be gone β€” boiled down to 40 bytes sitting in your memory and nothing more. The names Name, Email, and age are things you wrote for humans; the CPU never needed them. So where is this information coming from?<br>That&rsquo;s the whole question for today, and the answer has a satisfying shape: the reflect package doesn&rsquo;t compute any of this. It just reads it. The real work happened weeks ago, on your machine, when you typed go build.<br>A note on scope : This article focuses on the reading side of reflect β€” inspecting types and values. The package can also build things at runtime β€” new structs, function values, slices, and so on β€” and that&rsquo;s a whole story of its own that I&rsquo;ll probably come back to in a future article.

But a claim like that deserves a proper trace through the source β€” so let&rsquo;s start at the surface and dig down.<br>The Doorway: TypeOf and ValueOf<br>Most of our exploration starts at one of two doors: reflect.TypeOf<br>gives you a reflect.Type to ask questions about a type, and reflect.ValueOf<br>gives you a reflect.Value to inspect (and sometimes modify) an actual value. Both take an any as their argument. You&rsquo;d expect the entry points of something as powerful as reflection to be where the magic happens β€” so let&rsquo;s look at TypeOf:<br>func TypeOf(i any) Type {<br>return toType(abi.TypeOf(i))

Hmm, one level down then. Here&rsquo;s abi.TypeOf<br>func TypeOf(a any) *Type {<br>eface := *(*EmptyInterface)(unsafe.Pointer(&a))<br>return (*Type)(NoEscape(unsafe.Pointer(eface.Type)))

And that&rsquo;s it. No table lookup, no runtime call, no allocation. Take the address of the any parameter, reinterpret the memory sitting there as a struct called EmptyInterface, and return its first field. reflect.TypeOf is, at bottom, one pointer load .<br>Which can only mean one thing: the complete description of your type was already there, inside the any, before reflect ever got involved. The interesting part isn&rsquo;t the function β€” it&rsquo;s what an any actually is.<br>Inside an Interface<br>Here&rsquo;s what that EmptyInterface struct looks like (src/internal/abi/iface.go<br>):<br>type EmptyInterface struct {<br>Type *Type<br>Data unsafe.Pointer

Every any in your program β€” every empty interface value β€” is exactly this: two pointers . The second one points at the data. The first one points at a type descriptor : a struct that fully describes the dynamic type of whatever was stored in the interface (we&rsquo;ll get deeper into it in a moment). The runtime has its own mirror of this layout, called eface<br>So when you pass user to a function that takes an any β€” which is exactly what calling TypeOf is:<br>func TypeOf(i any) Type // the signature we just saw

TypeOf(user) // user becomes an any right here

the compiler emits code at the call site that stores two pointers: the address of User&rsquo;s type descriptor, and a pointer to the value. That&rsquo;s essentially the whole conversion β€” the data pointer just points at the value sitting somewhere in memory (on the stack or the heap), but the type pointer involves no lookup and no registration: it&rsquo;s a constant address the compiler knew at compile time.<br>This reframes our two entry points completely. TypeOf and ValueOf don&rsquo;t inspect your value at all. They read the interface header β€” the two pointers the compiler already put there.<br>But that just pushes the question one step back. The type pointer leads to a descriptor that knows field names and struct tags. Who built that descriptor,...

rsquo type reflect typeof pointer runtime

Related Articles