跪拜 Guibai
← Back to the summary

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:

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:

  1. reflect.ValueOf(age) gives a copy, so CanSet() is false, and SetInt will panic.
  2. reflect.ValueOf(&age).Elem() points to the original variable, so you can call SetInt / SetString / Set.
  3. Check before modifying: CanSet(), IsValid(), and whether Kind() is correct.
  4. 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 normal Age.

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:


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:

  1. Use Type to inspect types, use Value to manipulate data.
  2. To modify a value, pass a pointer and then call Elem().
  3. Reflection requires boxing/unboxing in and out: go in with ValueOf, come out with Interface() plus a type assertion.