Lab 03: Unsafe & Reflect

Time: 45 minutes | Level: Advanced | Docker: docker run -it --rm golang:1.22-alpine sh

Overview

Master Go's reflection system (reflect package) for runtime type inspection and manipulation. Understand unsafe.Pointer for low-level memory operations. Learn when (and when not) to use these powerful tools.


Step 1: reflect.TypeOf and reflect.ValueOf

package main

import (
	"fmt"
	"reflect"
)

type Point struct {
	X, Y float64
}

func inspectValue(v interface{}) {
	t := reflect.TypeOf(v)
	val := reflect.ValueOf(v)

	fmt.Printf("Type:  %v\n", t)
	fmt.Printf("Kind:  %v\n", t.Kind())
	fmt.Printf("Value: %v\n", val)

	switch t.Kind() {
	case reflect.Int, reflect.Int64:
		fmt.Printf("  int value: %d\n", val.Int())
	case reflect.String:
		fmt.Printf("  string len: %d\n", val.Len())
	case reflect.Struct:
		fmt.Printf("  struct fields: %d\n", t.NumField())
	case reflect.Slice:
		fmt.Printf("  slice len: %d\n", val.Len())
	}
}

func main() {
	inspectValue(42)
	inspectValue("hello")
	inspectValue(Point{1.5, 2.5})
	inspectValue([]int{1, 2, 3})
}

📸 Verified Output:


Step 2: Struct Field Inspection and Tags

Run it:

📸 Verified Output:


Step 3: reflect.Value.Set — Modify Struct Fields


Step 4: MethodByName — Dynamic Method Dispatch


Step 5: reflect.MakeFunc — Dynamic Function Creation


Step 6: unsafe.Pointer — Low-Level Memory

💡 uintptr is NOT a pointer — the GC doesn't trace it. Never store a uintptr across function calls when the object could be collected. Use unsafe.Pointer for temporary conversions only.


Step 7: unsafe.Slice and unsafe.String (Go 1.17+)


Step 8: Capstone — JSON-like Marshaler via Reflection

Run the capstone:

📸 Verified Output:


Summary

Feature
Use Case
Risk

reflect.TypeOf/ValueOf

Inspect types at runtime

Slow (10-100x vs direct)

reflect.Value.Set

Generic field setter

Panics on unexported fields

MethodByName

Plugin/event systems

No compile-time safety

reflect.MakeFunc

Adapters, RPC stubs

Complex to debug

unsafe.Pointer

Zero-copy conversions

GC can't trace; use carefully

unsafe.Slice/String

[]byte↔string no-alloc

Read-only for string→bytes

Key Takeaways:

  • reflect adds runtime overhead; use only when generics can't solve the problem

  • unsafe.Pointer conversions must follow strict rules (see Go spec)

  • Prefer unsafe.String/unsafe.Slice over unsafe.StringData+uintptr arithmetic

  • Test with -race when using unsafe — data races become crashes

Last updated