In Go, reflection refers to the ability of a program to inspect and manipulate its own types and values at runtime. While Go is a statically typed language designed for performance and simplicity, reflection introduces flexibility in situations where generic, dynamic behaviour is required.
Reflection is most useful when dealing with:
- Generic functions or data structures
- Serialization and deserialization (e.g., JSON, XML)
- Validation and field introspection (e.g., for forms or configs)
- Dependency injection
- Resetting or modifying values behind interfaces and pointers
đŚ The reflect Package
Goâs reflect package allows a program to manipulate objects with unknown types during runtime. This is crucial for writing code that operates across types.
import (
"fmt"
"reflect"
)
func inspect(i interface{}) {
t := reflect.TypeOf(i)
v := reflect.ValueOf(i)
fmt.Println("Type:", t)
fmt.Println("Kind:", t.Kind())
fmt.Println("Value:", v)
}
func main() {
var x float64 = 3.4
inspect(x)
}
â
Output:
Type: float64
Kind: float64
Value: 3.4
â
đ§° Modifying Values at Runtime
You can modify values, but only if they are addressable and settable (typically pointers):
func modify(i interface{}) {
v := reflect.ValueOf(i)
if v.Kind() == reflect.Ptr && v.Elem().CanSet() {
v.Elem().SetFloat(6.28)
}
}
func main() {
var x float64 = 3.4
modify(&x)
fmt.Println(x) // Output: 6.28
}
â
đ§ą Reflecting Over Structs
Structs are a key area where reflection really shinesâespecially when you want to write reusable or generic logic that works across types.
type User struct {
ID int
Name string
}
func printStructFields(input interface{}) {
v := reflect.ValueOf(input)
t := reflect.TypeOf(input)
if t.Kind() == reflect.Ptr {
v = v.Elem()
t = t.Elem()
}
if t.Kind() != reflect.Struct {
fmt.Println("Not a struct!")
return
}
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value.Interface())
}
}
func main() {
user := &User{ID: 1, Name: "Alice"}
printStructFields(user)
}
â
This kind of logic is common when implementing custom loggers, serializers, ORM layers, or data validators.
đ Resetting a Generic Struct Behind a Pointer
What if you need to reset a generic value thatâs behind a pointer? In a statically typed language like Go, you canât just say âset this generic to zero valueâ if youâre working through an interface or an unknown type parameter.
Hereâs how reflection can help:
func ResetValue(ptr interface{}) {
v := reflect.ValueOf(ptr)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
panic("ResetValue expects a pointer to a struct")
}
v.Elem().Set(reflect.Zero(v.Elem().Type()))
}
And using it:
type Config struct {
Enabled bool
Count int
}
func main() {
cfg := &Config{Enabled: true, Count: 42}
fmt.Println("Before:", *cfg)
ResetValue(cfg)
fmt.Println("After:", *cfg)
}
Output:
Before: {true 42}
After: {false 0}
â
This is incredibly useful for testing, reinitializing reused memory, or generic data pipelines where types arenât known at compile time but must be reset predictably.
đ§ Reflection + Generics
With Go 1.18+, generics reduce the need for reflection in many cases. But sometimes you still need to inspect or manipulate generic values at runtime.
func PrintGenericType[T any](val T) {
fmt.Println("Value:", val)
fmt.Println("Type:", reflect.TypeOf(val))
}
Great when youâre debugging or implementing advanced generic behaviour.
â
â ď¸ Use With Caution
Reflection is powerfulâbut with great power comes:
- Less safety (you lose compile-time guarantees)
- Reduced performance
- Reduced readability
Prefer interfaces or generics where possible. Use reflection only when nothing else fits, and always isolate its usage to small, well-tested parts of your codebase.
â