Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

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.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.