Go Reflection — Runtime Type Inspection and Manipulation with reflect Package
In this tutorial, you will learn about Go Reflection. We cover key concepts, practical examples, and best practices to help you master this topic.
Go reflect package enables runtime type inspection with TypeOf, ValueOf, and method calling for dynamic programming patterns.
What You'll Learn
- Type and value reflection
- Struct field inspection
- Dynamic method calling
- Reflection-based Serialization
Why It Matters
Reflection powers frameworks. JSON encoding uses reflection. ORM libraries use reflection. DodaZIP uses reflection for dynamic config loading.
Real-World Use
Serialization libraries, ORMs, testing frameworks, configuration loaders, Code Generation tools.
flowchart LR
A["Reflection"] --> B["TypeOf"]
A --> C["ValueOf"]
A --> D["Struct Tags"]
A --> E["Dynamic Call"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Type and Value Reflection
func inspect(v interface{}) {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
fmt.Println("Type:", t.Name())
fmt.Println("Kind:", t.Kind())
fmt.Println("Value:", val.Interface())
if t.Kind() == reflect.Ptr {
fmt.Println("Elem:", t.Elem().Name())
}
}
func main() {
inspect(42)
inspect("hello")
inspect(&struct{ Name string }{Name: "test"})
}
Struct Field Inspection
type User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field %d: %s\n", i, field.Name)
fmt.Printf(" Type: %s\n", field.Type)
fmt.Printf(" JSON tag: %s\n", field.Tag.Get("json"))
fmt.Printf(" Validate: %s\n", field.Tag.Get("validate"))
}
}
func main() {
inspectStruct(User{})
}
Dynamic Method Calling
type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Multiply(a, b int) int { return a * b }
func callMethod(obj interface{}, name string, args ...interface{}) []reflect.Value {
val := reflect.ValueOf(obj)
method := val.MethodByName(name)
if !method.IsValid() {
log.Fatalf("Method %s not found", name)
}
inputs := make([]reflect.Value, len(args))
for i, arg := range args {
inputs[i] = reflect.ValueOf(arg)
}
return method.Call(inputs)
}
func main() {
calc := Calculator{}
result := callMethod(calc, "Add", 3, 4)
fmt.Println("3 + 4 =", result[0].Interface())
}
Struct Tag Parsing
type Config struct {
Host string `default:"localhost" env:"HOST"`
Port int `default:"8080" env:"PORT"`
}
func loadConfig(v interface{}) {
val := reflect.ValueOf(v).Elem()
t := val.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := val.Field(i)
if env := field.Tag.Get("env"); env != "" {
if envVal := os.Getenv(env); envVal != "" {
fieldVal.SetString(envVal)
}
}
if def := field.Tag.Get("default"); def != "" && fieldVal.String() == "" {
fieldVal.SetString(def)
}
}
}
Reflection-Based Validator
func validate(v interface{}) error {
val := reflect.ValueOf(v)
t := val.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := val.Field(i)
rules := field.Tag.Get("validate")
if strings.Contains(rules, "required") && fieldVal.String() == "" {
return fmt.Errorf("%s is required", field.Name)
}
if strings.Contains(rules, "email") {
email := fieldVal.String()
if !strings.Contains(email, "@") {
return fmt.Errorf("%s must be valid email", field.Name)
}
}
}
return nil
}
Common Mistakes
1. Passing Non-Pointer for Modification
func setValue(v interface{}) {
val := reflect.ValueOf(v)
val.Elem().SetString("new") // Panics if v is not a pointer
}
2. Calling Unexported Methods
reflect.MethodByName only finds exported methods. Unexported methods are invisible.
3. Not Checking IsValid
method := val.MethodByName("Foo")
if !method.IsValid() { /* handle missing method */ }
4. Reflection Performance
Reflection is slower than direct calls. Cache reflect results. Use code generation when possible.
5. Panic on Type Mismatch
Always check Kind() before calling specialized setters like SetString or SetInt.
Practice Questions
1. What is the difference between TypeOf and ValueOf? TypeOf returns reflect.Type (static type info). ValueOf returns reflect.Value (runtime value access).
2. How do you set a value via reflection? Get Elem() of pointer, then call SetString, SetInt, SetFloat, or Set on the Value.
3. What are struct tags? Key-value pairs in struct field declarations. Accessed via field.Tag.Get("key"). Used by JSON, GORM, validators.
4. Can reflection access unexported fields? Yes, via unsafe or reflect.Value.Elem(). But not recommended. Unexported fields should be private.
Challenge: Write a function that deep copies a struct using reflection.
Solution
func deepCopy(src interface{}) interface{} {
srcVal := reflect.ValueOf(src)
srcType := srcVal.Type()
dst := reflect.New(srcType).Elem()
for i := 0; i < srcType.NumField(); i++ {
dst.Field(i).Set(srcVal.Field(i))
}
return dst.Interface()
}
FAQ
{{< faq question="When should I use reflection?" >}} When writing libraries that handle arbitrary types: serialization, ORM, config loaders. Avoid reflection in application business logic. {{< /faq >}}
{{< faq question="Is reflection slow?" >}} Yes, compared to direct calls. The overhead is acceptable for initialization code. Cache reflect.Type and reflect.Value for repeated use. {{< /faq >}}
{{< faq question="Can I create new types at runtime?" >}} Not directly. Reflection inspects existing types. Use code generation (go generate) for compile-time type creation. {{< /faq >}}
{{< faq question="What is unsafe.Pointer vs reflect?" >}} unsafe bypasses type safety entirely. reflect is type-safe (panics on misuse). Use unsafe only in extreme performance-critical code. {{< /faq >}}
{{< faq question="How do I check if a value implements an interface?" >}}
Use reflect.Type.Implements(interfaceType) or type assertion v.(MyInterface).
{{< /faq >}}
Try It Yourself
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
v := reflect.ValueOf(x)
fmt.Println("Type:", v.Type())
fmt.Println("Kind:", v.Kind())
fmt.Println("Int:", v.Int())
}
Expected output:
Type: int
Kind: int
Int: 42
What's Next
Now that you understand reflection, explore CGo for calling C code from Go.
| Topic | Description | Link |
|---|---|---|
| Go CGo | C interop | {{< ref "33-cgo" >}} |
| Go Generics | Type parameters | {{< ref "31-generics" >}} |
| Go Testing Advanced | Advanced testing | {{< ref "34-testing-advanced" >}} |