Go > Reflection and Generics > Reflection > reflect package basics
Inspecting and Modifying Variables with Reflection in Go
This example demonstrates the fundamental usage of the `reflect` package in Go for inspecting and modifying variables at runtime. It covers getting the type and value of a variable, checking its kind, and setting new values using reflection.
Getting Started with Reflection
This initial snippet introduces the basic functions provided by the `reflect` package. We start by declaring a variable `x` of type `float64`. We then use `reflect.TypeOf()` to get the type of `x`, `reflect.ValueOf()` to get its value, and `v.Kind()` to determine its underlying kind. Finally, we retrieve the value as an `interface{}` using `v.Interface()` and convert it back to `float64` using a type assertion.
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
// Get the type of the variable
t := reflect.TypeOf(x)
fmt.Println("Type:", t)
// Get the value of the variable
v := reflect.ValueOf(x)
fmt.Println("Value:", v)
// Get the kind of the variable
k := v.Kind()
fmt.Println("Kind:", k)
// Get the value as an interface{}
i := v.Interface()
fmt.Println("Interface value:", i)
// Convert the interface{} back to float64
y := i.(float64)
fmt.Println("Converted value:", y)
}
Modifying a Variable with Reflection
This snippet demonstrates how to modify a variable using reflection. Crucially, to modify a variable, you need to work with a *pointer* to that variable. We obtain the `reflect.Value` of a pointer to `x` using `reflect.ValueOf(&x)`. Then, we use `v.Elem()` to get the `reflect.Value` of the variable the pointer points to. The `CanSet()` method is used to verify if the reflected value can be modified. If it can, we use `SetFloat()` to set a new value.
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(&x)
// Check if the value is settable
if v.CanSet() {
fmt.Println("Value can be set")
} else {
fmt.Println("Value cannot be set")
}
// Get the value of the pointer using Elem()
v = v.Elem()
// Check if the value is settable after Elem()
if v.CanSet() {
fmt.Println("Value can be set after Elem()")
// Set the value using SetFloat()
v.SetFloat(7.1)
fmt.Println("New value of x:", x)
} else {
fmt.Println("Value cannot be set after Elem()")
}
}
Concepts Behind the Snippet
Reflection in Go allows you to inspect and manipulate types and values at runtime. This is achieved through the `reflect` package, which provides functions to obtain the type and value of variables. The `reflect.Type` represents the type of a Go value, and `reflect.Value` represents the value itself. Understanding the concept of 'settability' is critical; a `reflect.Value` can only be modified if it represents a settable value, which typically means it was obtained from a pointer to the original variable.
Real-Life Use Case
A common real-life use case is in ORM (Object-Relational Mapping) libraries. These libraries often use reflection to dynamically map database rows to Go structs, setting the values of the struct fields based on the data in the database. Another use case is building generic functions that can work with different types of data without knowing the type at compile time, such as a generic serializer/deserializer.
Best Practices
Interview Tip
When discussing reflection in an interview, be sure to highlight your understanding of the performance implications and the importance of checking settability before modifying values. Also, mentioning real-world use cases, such as ORMs or generic data processing, demonstrates a practical understanding of the concept.
When to Use Them
Use reflection when you need to write code that operates on types or values that are not known at compile time. Examples include generic data structures, serialization/deserialization routines, and metaprogramming tasks.
Memory Footprint
Reflection operations can have a higher memory footprint compared to direct operations. This is because the `reflect` package needs to store additional metadata about the types and values being reflected upon. Consider the impact on memory usage when using reflection in performance-critical applications.
Alternatives
Alternatives to reflection include using interfaces, type switches, and code generation. Interfaces provide a way to write generic code that works with different types, while type switches allow you to handle different types explicitly. Code generation can be used to generate specific code for each type at compile time, avoiding the overhead of reflection at runtime.
Pros
Cons
FAQ
-
What is `reflect.Value.Elem()`?
`reflect.Value.Elem()` returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. -
Why do I need to use a pointer to modify a value using reflection?
To modify a value using reflection, you need a `reflect.Value` that represents a settable value. A settable value is one that was obtained from a pointer. When you pass a variable by value, you are working with a copy, so any changes made through reflection would not affect the original variable. -
What does `CanSet()` do?
`CanSet()` reports whether the value of v can be changed. Value can be changed if it is addressable and was not obtained by the use of unexported struct fields.