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
206: Reflection with the reflect Package
Reflection is the "break glass in case of emergency" tool in Go. Most of the time, you want to avoid it because it bypasses type safety, slows down your code, and makes things harder to read. But there are times—like when you're writing a generic database mapper or a JSON validator—where you simply cannot know the type of the data you're dealing with until the program is actually running.
To understand how the reflect package works, let's build a simple "Required Field Validator." Imagine we want to mark certain fields in a struct with a tag like validate:"required", and then have a single function that can check any struct to see if those fields are empty.
Peeking inside the interface
First, we need a way to pass any struct into our validator. That means our function signature has to take an interface{} (or any in newer Go versions). To actually see what's inside that interface, we use reflect.ValueOf and reflect.TypeOf.
type User struct {
Name string `validate:"required"`
Email string `validate:"required"`
Age int
}
func Validate(s any) error {
v := reflect.ValueOf(s)
t := reflect.TypeOf(s)
// We want to iterate over the fields of the struct
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
if field.Tag.Get("validate") == "required" {
// Check if the value is the zero value for its type
if value.IsZero() {
return fmt.Errorf("field %s is required", field.Name)
}
}
}
return nil
}
Hitting a wall with pointers
Now, if I run this with Validate(User{Name: "Alice"}), it works perfectly. But in a real app, we almost always pass pointers to structs to avoid copying large objects. I tried running it with &User{Name: "Alice"}, and the program immediately panicked: reflect: call of reflect.Value.NumField on ptr.
I realized I made a classic reflection mistake. reflect.ValueOf(s) gives me the value of the interface. If I pass a pointer, v is a pointer, not a struct. You can't call NumField() on a pointer; you have to "dereference" it first to get to the actual struct underneath.
Here is how I fixed it. I need to check if the kind of the value is a pointer, and if so, call .Elem() to get the value the pointer points to.
func Validate(s any) error {
v := reflect.ValueOf(s)
// Fix: If it's a pointer, get the element it points to
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// Now we get the type from the value, ensuring they stay in sync
t := v.Type()
if t.Kind() != reflect.Struct {
return fmt.Errorf("Validate only works on structs")
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
if field.Tag.Get("validate") == "required" {
if value.IsZero() {
return fmt.Errorf("field %s is required", field.Name)
}
}
}
return nil
}
Handling different types of "empty"
The value.IsZero() method is incredibly handy here because it doesn't care if the field is a string, an int, or a slice; it just checks if it's the default zero value for that specific type. This is where reflection actually shines—we've written a single loop that handles any data type we throw at it, provided it's wrapped in a struct.
One last thought: notice how I moved t := v.Type() after the pointer check. This is important. If you call reflect.TypeOf on a pointer, you get a pointer type. If you call it on the result of .Elem(), you get the struct type. Keeping them aligned prevents a lot of headaches.
📋 Practical Task
Build a "Secret Masker" Using Reflection
Your task is to create a function called MaskSecrets(obj any). This function should take a pointer to a struct and look for fields tagged with secret:"mask". Instead of returning an error, this function should actually modify the struct, replacing the value of any "masked" string field with "****".
Requirements:
- The function must handle pointers to structs (use
.Elem()). - It must check for the
secret:"mask"tag. - It must only attempt to mask fields that are of type
string(usevalue.Kind() == reflect.String). - You must use
value.SetString("****")to change the value. (Hint: Remember that forSetStringto work, the value must be addressable, which is why we pass a pointer to the struct).
Test Case:
type Config struct {
APIKey string `secret:"mask"`
Port int
Pass string `secret:"mask"`
}
cfg := &Config{APIKey: "12345-ABC", Port: 8080, Pass: "super-secret"}
MaskSecrets(cfg)
// Result: cfg.APIKey should be "****" and cfg.Pass should be "****"
There are no comments for now.