I needed fast reflection for a project โ stdlib reflect was showing up clearly on the profiler in hot paths like ORM scanning and DI. So I started looking at what already existed, and came across reflect2.
The idea there is simple: instead of going through the official reflect API, the library reads Go's internal struct layout directly. That's fast. Right up until Go changes those internals. And it does โ the map rewrite to Swiss Tables in Go 1.24 is a good example. When that happens, the library doesn't crash, doesn't panic, doesn't throw an error. It just quietly reads the wrong bytes. You won't notice right away โ maybe a week later, maybe two months later, when weird values start showing up in production and you spend half a day figuring out where they came from.
That risk didn't sit well with me, so I decided to build the same thing, minus it.
What I ended up with
saferefl aims for the same kind of speed, but through a more honest route: generics, cached field offsets, and an unsafe layer that verifies its own assumptions on startup and falls back to a safe path if something doesn't line up โ instead of silently reading memory it shouldn't.
In practice it looks like this:
import "github.com/lkmavi/saferefl"
type User struct {
Name string
Age int
}
u := &User{Name: "Alice", Age: 30}
name, err := saferefl.Get[string](u, "Name") // "Alice", nil
_ = saferefl.Set[int](u, "Age", 31)
No interface{}, no manual type assertions โ the compiler makes sure you're reading a string as a string, not as an int.
How it's put together
Roughly three layers, stacked:
-
Generic API โ the part you actually use:
Get[T]/Set[T], dot-path support ("Office.City") for nested structs, and safe handling of pointers (a nil pointer is an error, not a panic in production). -
Type info cache โ the first time you touch a type, its metadata gets built once via stdlib
reflect. After that, everything comes from cache with zero allocations. - Accessor API โ for the genuinely hot paths. If you're scanning thousands of DB rows per second into structs, you bind the field path once and then access it almost for free:
ageAcc, _ := saferefl.MakeAccessor[int](u, "Age")
ptr := saferefl.UnsafePtrOf(u)
age := ageAcc.Get(ptr) // 0.55 ns, zero allocations
ageAcc.Set(ptr, 31)
The numbers
Benchmarked on an M3 Max, Go 1.26:
| Scenario | reflect | saferefl | Accessor |
|---|---|---|---|
| Field read | 26.8 ns | 21.2 ns | 0.555 ns |
| JSON decode (per field) | 719 ns | 232 ns | 3.04 ns |
| ORM scan | 452 ns | 236 ns | 5.51 ns |
The part I'm actually happy with: Accessor on ORM scanning and struct copying lands within 1.1โ1.3ร of hand-written code. Basically as close as you'd get to writing u.Age = row.Age by hand โ except it works for any field, looked up by name.
And Get/Set already beat reflect.FieldByName on the very first call. No warm-up loop required to make the library "fast."
What actually separates this from reflect2
It's not really about raw speed โ the numbers are in the same ballpark. It's about what happens when something doesn't go as expected.
reflect2 silently corrupts data in that case. saferefl checks its layout assumptions at init() time, and if something doesn't match, it falls back to a safe path instead of reading memory blindly. There are two map backends built in from the start โ the old hmap and the newer Swiss Tables โ so the Go 1.24 map rewrite wasn't a surprise, it was something the design already accounted for.
If you want the full reasoning behind these choices, I wrote it up in ADR-01.
Where things stand
This is v0.1.0 โ an honest MVP. The core API is implemented and tested, CI runs across Go 1.22 through tip, benchmarks regenerate automatically. But the 0.x version isn't just a formality โ I'm not ready to promise API stability until it's seen some real usage outside my own projects.
If you've got a hot path that touches struct fields by name โ ORM, DI, serialization โ give it a try, I'd genuinely be curious what it looks like on your workload:
go get github.com/lkmavi/saferefl
Repo: github.com/lkmavi/saferefl
If you hit a bug, find an edge case, or just want to share thoughts โ issues are open. And a star is always appreciated :)













