32. Reflection and unsafe: when, and why rarely
32.1 What reflection is actually for
Reflection (reflect package) is the right tool when you're writing a
generic library that must operate on arbitrary, caller-defined types
it cannot know at compile time: encoding/json, encoding/xml,
dependency-injection containers, ORMs, struct validators. It is the wrong
tool for application-level logic, where a type switch or generics almost
always express intent more clearly and run faster.
32.2 unsafe: escaping Go's type system entirely
unsafe.Pointer lets you reinterpret memory across types, bypassing the
type checker and the memory safety guarantees Go otherwise provides. It
exists for a few legitimate uses — implementing extremely low-level
data-structure tricks in the standard library itself, some cgo
interop, and zero-copy conversions like unsafe.String/unsafe.Slice
(Go 1.20+, replacing the older raw-pointer-arithmetic idiom) — and it is
explicitly exempted from Go's backward-compatibility promise. If you
find yourself reaching for unsafe in application code, that's a strong
signal to look for a safe alternative first.
// A legitimate, narrow use: zero-copy []byte -> string when you can
// prove the []byte will never be mutated afterward.
func unsafeString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
32.3 Real-world example
protobuf, gRPC, and high-throughput serialization libraries use
unsafe internally to avoid copying bytes between a wire buffer and a Go
string — but they hide it behind a safe public API. That's the correct
shape: unsafe stays an implementation detail of a well-tested library,
never a tool reached for casually in a service handler.