Go's reflect Package: Three Patterns for Runtime Type, Value, and Method Access
Reflection
The previous chapter covered custom types. A type is fixed at compile time, but how do you "see" at runtime what type a value actually is, whether it can be changed, or whether it has a certain method?
The answer is the standard library reflect: it inspects types, reads and writes values, and calls methods at runtime. Frameworks, ORMs, and JSON codecs rely on it heavily.
Reflection is flexible, but the code is more convoluted and also slower. For daily business logic, if interfaces or generics can solve the problem, prefer those over reflection.
Below, we use the same custom type Age for three examples:
type Age int
1. Using reflection for type inspection
First get the type information, then see the "name" and the "underlying kind".
package main
import (
"fmt"
"reflect"
)
type Age int
func main() {
var age Age = 99
t := reflect.TypeOf(age)
fmt.Println(t.Name()) // Age —— type name (the name of the custom type)
fmt.Println(t.Kind()) // int —— underlying kind (used for condition checks)
if t.Kind() == reflect.Int {
fmt.Println("It is definitely an int type")
}
}
| API | Purpose | Result in this example |
|---|---|---|
reflect.TypeOf(x) |
Get the type description of x |
reflect.Type of Age |
t.Name() |
Type name | "Age" |
t.Kind() |
Underlying kind (int / string / struct…) | reflect.Int |
Don't confuse Name and Kind:
Name(): the name you gave (e.g.,Age)Kind(): the actual underlying form (e.g.,int)
For branching logic, almost always compare Kind(), for example reflect.Int, reflect.String, reflect.Struct.
2. Modifying values via reflection + type assertion
To modify the original variable, you must pass a pointer and then use .Elem() to dereference, obtaining a writable reflect.Value.
package main
import (
"fmt"
"reflect"
)
type Age int
func main() {
var age Age = 99
// Must pass &age, then Elem(); otherwise CanSet() is false and it can't be changed
val := reflect.ValueOf(&age).Elem()
// val.CanSet() whether modification is allowed
// val.IsValid() whether it is a valid reflect.Value
if val.CanSet() && val.IsValid() && val.Kind() == reflect.Int {
val.SetInt(10086) // write via reflection
}
// Type assertion: Interface() first turns it into any, then .(Age) restores the concrete type
v := val.Interface().(Age)
fmt.Println(val) // 10086 (reflect.Value; printing shows the internal value)
fmt.Println(age) // 10086 (the original variable has been modified)
fmt.Println(v) // 10086 (the Age value restored by assertion)
}
Key points:
reflect.ValueOf(age)gives a copy, soCanSet()isfalse, andSetIntwill panic.reflect.ValueOf(&age).Elem()points to the original variable, so you can callSetInt/SetString/Set.- Check before modifying:
CanSet(),IsValid(), and whetherKind()is correct. - Type assertion:
val.Interface().(Age)takes the value from the reflection world back to the static type, after which it can be used as a normalAge.
A safer assertion (with ok):
v, ok := val.Interface().(Age)
if !ok {
fmt.Println("assertion failed")
return
}
fmt.Println(v)
3. Calling methods via reflection
Attach a method to Age, then call it dynamically by name.
package main
import (
"fmt"
"reflect"
)
type Age int
func (a Age) Say(params string, n int) {
fmt.Println(params, n)
}
func main() {
var age Age = 99
val := reflect.ValueOf(age)
m := val.MethodByName("Say") // find the method by name (must be exported, first letter capitalized)
if m.IsValid() {
m.Call([]reflect.Value{
reflect.ValueOf("我是字符串"),
reflect.ValueOf(123123),
})
// Output: 我是字符串 123123
}
}
Breaking down the process:
| Step | Code | Meaning |
|---|---|---|
| 1 | reflect.ValueOf(age) |
Get the reflection wrapper of the value |
| 2 | MethodByName("Say") |
Find the method by name; if not found, IsValid() is false |
| 3 | m.Call([]reflect.Value{...}) |
Pass the argument list (each argument must be wrapped with ValueOf) |
Notes:
- The method name must be exported (capitalized), otherwise
MethodByNamewon't find it. - The number and types of arguments passed to
Callmust match the method signature, otherwise it panics. - When there are return values,
Callreturns[]reflect.Value, and you can useInterface()to extract them as needed.
Summary
| Case | Core API | Purpose |
|---|---|---|
| Type inspection | TypeOf / Name / Kind |
Recognize the type at runtime |
| Value modification | ValueOf(&x).Elem() + SetInt + Interface().(T) |
Modify the original variable, then assert back to a concrete type |
| Method invocation | MethodByName + Call |
Call dynamically by name |
Remember the three skills:
- Use
Typeto inspect types, useValueto manipulate data. - To modify a value, pass a pointer and then call
Elem(). - Reflection requires boxing/unboxing in and out: go in with
ValueOf, come out withInterface()plus a type assertion.