reflect2

A Go reflection API that avoids the runtime cost of reflect.Value by working with unsafe pointers directly.

Library
Go
vv1.0.2
827stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
42/100Fair
Development Activity0
Maintenance20
Community60
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
62/100Good
Architecture78
Code Quality45
Innovation70
Learning Curve55

reflect2 re-implements the parts of Go’s standard reflect package that matter for high-performance serialization code, but skips the boxing and dispatch overhead that comes with reflect.Value. Instead it exposes a Type interface backed either by raw unsafe.Pointer operations (the default, fastest path) or by a pure-reflect-based safe fallback, so callers can choose speed versus portability at configuration time.

It was extracted from json-iterator/go to be reused anywhere Go code needs to get or set values, walk struct fields, or resolve types by name at runtime without paying reflect’s usual performance tax. The package is intentionally low-level and is meant to be a building block inside other libraries rather than something application code reaches for directly.

What You Get

  • A Type interface with unsafe get/set, New/UnsafeNew, and PackEFace/Indirect operations that avoid allocating and unwrapping reflect.Value
  • reflect2.TypeByName and TypeByPackageName, a Class.forName-style runtime type lookup built on the Go runtime’s type-link tables
  • Specialized interfaces for structs, slices, arrays, maps, and pointers (StructType, SliceType, MapType, PtrType) with typed field/index/key accessors
  • A Config/Froze() pattern (ConfigUnsafe / ConfigSafe) that lets callers switch the whole API between the unsafe fast path and a pure-reflect safe path
  • Helpers like reflect2.IsNil, PtrOf, RTypeOf, and NoEscape for working directly with Go’s internal eface/iface representations

Common Use Cases

  • Building a JSON, MessagePack, or other binary encoder/decoder that needs to set struct fields and slice elements without reflect.Value allocation overhead
  • Implementing a serialization library that must support a safe, pure-Go fallback for environments where unsafe operations are restricted
  • Writing an ORM or struct-mapping library that walks struct tags and fields at high throughput
  • Looking up a type by its fully-qualified name at runtime, similar to Java’s Class.forName, for plugin or registry systems
  • Any low-level Go library that repeatedly reflects over the same types and wants to cache type metadata behind a stable interface

Under The Hood

Architecture reflect2 is organized as a thin dispatch layer (reflect2.go) that wraps a reflect.Type into one of several concrete implementations chosen by frozenConfig.wrapType based on reflect.KindunsafeStructType, unsafeSliceType, unsafeMapType, unsafePtrType, unsafeEFaceType/unsafeIFaceType, or a generic unsafeType, each with a safe* counterpart used when Config.UseSafeImplementation is set. Every wrapped type is cached in a sync.Map keyed by the runtime type pointer inside frozenConfig, so repeated TypeOf/Type2 calls for the same type are effectively free after the first. type_map.go adds a separate, lazily-initialized global registry (types, packages) built once via typelinks2, the runtime’s linker-generated type-link table, giving TypeByName/TypeByPackageName their name-based lookup. What breaks if the core abstraction changes: any code relying on the internal eface/iface struct layout (unpackEFace, packEFace) is tied directly to Go’s runtime memory layout for interface values, so the library must be updated in lockstep with changes to that layout.

Tech Stack The project is pure Go (module github.com/modern-go/reflect2, go.mod declares go 1.12) with zero runtime dependencies beyond the standard library (reflect, runtime, sync, unsafe). Build-tag-gated files (go_above_118.go, go_below_118.go, go_above_19.go) adapt to internal reflect/runtime layout changes across Go versions, and a !gccgo build constraint on type_map.go acknowledges that the typelinks2/linkname trick is specific to the standard gc toolchain. A legacy Gopkg.toml/Gopkg.lock pair shows the project predates Go modules and was originally vendored via dep; CI is configured through a minimal .travis.yml.

Code Quality No test files (_test.go) exist in this repository — behavioral verification for reflect2 is left entirely to its consumers, most notably json-iterator/go’s own extensive test suite exercising this package indirectly. Code style is consistent and narrowly scoped: each Go kind (struct, slice, map, array, pointer, interface) gets its own small file pair (unsafe_*.go / safe_*.go), naming mirrors the standard reflect package’s vocabulary closely, and panics via assertType are used for programmer-error type mismatches rather than returned errors, which is a deliberate low-level-library tradeoff rather than an oversight.

What Makes It Unique Rather than wrapping reflect.Value and eating its allocation and dynamic-dispatch cost on every get/set, reflect2 caches a wrapped-type object once and then reuses raw unsafe.Pointer operations (typedmemmove, unsafe_New) for subsequent field access — trading the standard library’s safety checks for throughput in a narrow, well-documented way, while still shipping a same-interface safe implementation so callers aren’t locked into unsafe code paths.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search