Reflection in Go: Inspecting and Changing Values at Runtime
Chapter 06 of 06

Reflection in Go: Inspecting and Changing Values at Runtime

reflect.TypeOf and ValueOf, Type vs Kind, setting values through pointers, reading struct tags, and when not to use reflection.

Sabbir Ahmmed3 min read0
On this page

Reflection lets a program inspect and modify values whose types are only known at runtime. You rarely write reflection yourself, but you use it all the time: encoding/json, fmt, validators and ORMs are built on the reflect package. Understanding it explains how struct tags work and why some operations panic.

Type and Value

Two functions are the entry points:

reflect.TypeOf(x) returns a reflect.Type: what the value is.
reflect.ValueOf(x) returns a reflect.Value: the value itself, with methods to read (and sometimes write) it.
go
1
2
3
4
5
6
7
8
9
x := 10
v := reflect.ValueOf(x)
t := v.Type()

fmt.Println(v)                       // 10
fmt.Println(v.IsZero())              // false
fmt.Println(t)                       // int
fmt.Println(t.Kind())                // int
fmt.Println(t.Kind() == reflect.Int) // true

Type vs Kind

The Type is the exact type, including your custom names. The Kind is the underlying category: Int, String, Struct, Slice, Map, Ptr and so on.

go
1
2
3
4
5
6
type UserID int

var id UserID = 7
t := reflect.TypeOf(id)
fmt.Println(t)        // main.UserID
fmt.Println(t.Kind()) // int

Generic reflection code usually switches on Kind, because it cares about the shape, not the name.

Changing a value at runtime

To modify a variable through reflection, you must reflect on a pointer and then call Elem() to reach the value it points to:

go
1
2
3
4
5
6
7
8
9
10
id := 101
p := reflect.ValueOf(&id) // a *int
e := p.Elem()             // the int it points to

fmt.Println(p.Type())  // *int
fmt.Println(e.Int())   // 101
fmt.Println(e.CanSet()) // true

e.SetInt(100)
fmt.Println(id) // 100, the original variable changed

Reflecting on the value directly gives you a copy that cannot be set:

go
1
2
reflect.ValueOf(id).SetInt(5)
// panic: reflect: reflect.Value.SetInt using unaddressable value

It is the same rule as function arguments: to change something, you need its address.

Reading struct fields and tags

This is how encoding/json knows to use "name" instead of Name. Struct tags are metadata strings that reflection can read:

tags.go
1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
	Name  string `json:"name" validate:"required"`
	Email string `json:"email"`
	age   int
}

t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
	f := t.Field(i)
	fmt.Printf("%s json=%q validate=%q exported=%v\n",
		f.Name, f.Tag.Get("json"), f.Tag.Get("validate"), f.IsExported())
}
Output
1
2
3
Name json="name" validate="required" exported=true
Email json="email" validate="" exported=true
age json="" validate="" exported=false

A validator library reads the validate tag exactly like this, and JSON ignores age because unexported fields cannot be accessed from another package.

Working with unknown values

With any, reflection can discover what you received:

go
1
2
3
4
5
var unknown any = "This is an unknown type"
v := reflect.ValueOf(unknown)
if v.Kind() == reflect.String {
	fmt.Println("string:", v.String())
}

For a fixed set of possibilities, a type switch is simpler, faster and type-safe; reach for reflection only when the types are truly open-ended.

Costs and cautions

Warning
Reflection is slower than normal code, moves type errors from compile time to runtime panics, and makes code harder to read. Prefer interfaces, generics or type switches. Use reflection for genuinely generic infrastructure: serializers, validators, dependency injection, test helpers.

Common panics to watch for:

Calling SetInt on an unaddressable value (forgot the pointer and Elem()).
Calling Int() on a value whose kind is not an integer.
Setting an unexported field (CanSet() returns false).
Calling methods on the zero reflect.Value (for example, ValueOf(nil)).

Key takeaways

TypeOf tells you the type; ValueOf gives you the value.
Type is the exact name; Kind is the underlying category.
To modify through reflection: reflect.ValueOf(&x).Elem().
Struct tags are read with Field(i).Tag.Get("key"); this powers JSON and validation.
Use reflection only where interfaces, generics and type switches cannot do the job.
#Golang#Reflection#Advanced

Last updated on

Discussion0