Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
59: Reflection in Go
I've noticed a recurring pattern with developers moving into Go: they treat the reflect package like a "get out of jail free" card for type safety. The biggest misconception I see is the belief that reflect is just a fancy way to check a variable's type at runtime—sort of like a typeof operator in JavaScript—and that you can use it to bypass Go's strict typing whenever you're feeling lazy.
Here is why that's a dangerous way to think. If you treat reflection as a simple inspection tool, you'll eventually try to change a value you've reflected and find yourself staring at a panic: reflect: reflect.Value.SetInt using value obtained from unaddressable object. You can't just "reach in" and change a variable because you have a reflect.Value; Go's memory safety rules still apply, and reflection is no exception.
It's a runtime surgery tool, not just a type-checker
In Go, reflection is split into two main concepts: reflect.Type and reflect.Value. The Type tells you what the thing is (e.g., "this is a slice of ints"), while the Value lets you actually look at or manipulate the data inside.
Let's look at a real scenario. Imagine you're writing a library that needs to print all the fields of a struct, regardless of what that struct is. You can't write a separate function for every single struct in your codebase—that's insanity. This is where reflection actually shines.
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
func PrintFields(i interface{}) {
v := reflect.ValueOf(i)
t := reflect.TypeOf(i)
// If it's a pointer, we need to get the element it points to
if v.Kind() == reflect.Ptr {
v = v.Elem()
t = t.Elem()
}
if v.Kind() != reflect.Struct {
fmt.Println("I can only print fields of structs!")
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() {
u := User{"Alice", 30}
PrintFields(u)
}
In this example, I'm using reflect.ValueOf to get the data and reflect.TypeOf to get the metadata (the field names). Notice how I had to check if the input was a pointer. If you forget that, your code will panic the moment someone passes &u instead of u. Reflection is powerful, but it's fragile; you're essentially telling the compiler, "Trust me, I know what I'm doing," and the compiler responds by removing the safety net.
The Addressability Trap
Now, let's address that panic I mentioned earlier. A common mistake is trying to modify a value using reflection without providing a pointer. In Go, when you pass a variable to an interface{}, you're often passing a copy of that value. If you reflect on a copy, you can't change the original.
If you want to change a value via reflection, you must pass a pointer to the variable and then call .Elem() on the resulting reflect.Value. This "dereferences" the pointer in the reflection world, giving you a Value that is "addressable."
func SetIntField(obj interface{}, value int) {
// This will panic if obj is not a pointer!
v := reflect.ValueOf(obj).Elem()
// We assume we know the first field is an int for this example
f := v.Field(0)
if f.CanSet() {
f.SetInt(int64(value))
}
}
I'll be honest: I rarely use reflect in my day-to-day feature work. Since Go 1.18, Generics handle about 90% of the use cases where people used to reach for reflection. Use reflection when you are building something truly generic—like a JSON encoder, a database ORM, or a deep-equality test—but avoid it if you're just trying to avoid writing a few type-switch cases. The performance hit is real, and the code becomes significantly harder to read and debug.
📋 Practical Task
Building a Dynamic Struct Zero-Value Reset-ter
Your task is to create a function called ResetZeroFields. This function should take a pointer to any struct and a slice of field names (strings). For every field name provided in the slice, the function should check if that field exists in the struct and, if it does, reset it to its "zero value" (e.g., "" for strings, 0 for ints, nil for slices).
Requirements:
- The function signature must be:
func ResetZeroFields(obj interface{}, fields []string). - It must handle pointers to structs (use
.Elem()). - It must check if a field is "settable" before attempting to change it to avoid panics.
- If a field name is passed that doesn't exist in the struct, the function should simply ignore it.
Test Case:
type Profile struct {
Username string
Bio string
Age int
}
p := &Profile{Username: "GoExpert", Bio: "I love reflection!", Age: 10}
ResetZeroFields(p, []string{"Bio", "Age", "NonExistentField"})
// Expected result: p.Username remains "GoExpert", p.Bio becomes "", p.Age becomes 0.
There are no comments for now.