Saltar a contenido

Week 13 - Reflection: reflect, Performance, and Discipline

13.1 Conceptual Core

  • The reflect package exposes the runtime type system: every Go value has a reflect.Type (its static type) and a reflect.Value (a wrapper holding the value plus its type). Together they let you inspect and manipulate values whose concrete type is known only at runtime.
  • The two reflection use cases that matter:
  • Generic serialization / deserialization (encoding/json, encoding/gob, gorm, sqlx)-when the input is any.
  • Schema-driven adapters-config loaders, ORM tag parsers, validators.
  • Reflection is slow. Roughly 5–50× the cost of direct field access. The standard libraries that use it (encoding/json) compensate by caching reflect.Type lookups and method tables per type.

13.2 Mechanical Detail

  • reflect.Type is comparable (==) by identity-two reflect.Type values are equal iff they describe the same Go type. This makes map[reflect.Type]Cache a load-bearing pattern.
  • reflect.Value.Kind() returns the underlying kind (Struct, Ptr, Slice, etc.). reflect.Value.Type() returns the named type. The two differ for named types: type MyInt int has Kind Int, Type MyInt.
  • Field iteration: t.NumField(), t.Field(i) returns a StructField with Name, Type, Tag, Index, Anonymous, PkgPath. Tag.Get("json") is the canonical tag-parsing path.
  • Method invocation: v.Method(i).Call([]reflect.Value{...}). Allocates the slice and the result.
  • unsafe.Pointer shortcut: for performance-critical reflection, take the field address via unsafe.Pointer(v.Field(i).UnsafeAddr()) and read it as the typed value. This is what mapstructure and high-performance JSON libraries do internally. Read the safety contract carefully-it's narrow.
  • Caching pattern:
    var typeInfoCache sync.Map // reflect.Type -> *typeInfo
    func infoFor(t reflect.Type) *typeInfo {
        if v, ok := typeInfoCache.Load(t); ok { return v.(*typeInfo) }
        info := buildInfo(t)            // expensive
        typeInfoCache.Store(t, info)
        return info
    }
    

13.3 Lab-"A Reflective Validator"

Build a struct validator that processes validate:"..." tags: - Must support: required, min=N, max=N, email, regexp=<re>. - Must cache per-type field metadata (one reflect.Type walk per type ever). - Must produce structured errors (path, rule, value). - Must beat a naive non-cached implementation by 10× in benchmarks.

Compare against go-playground/validator for both ergonomics and performance.

13.4 Idiomatic & golangci-lint Drill

  • staticcheck SA1019 (deprecated reflect APIs), gocritic: hugeParam. The pattern of accepting any then immediately calling reflect.ValueOf is a smell-prefer typed APIs whenever possible.

13.5 Production Hardening Slice

  • Add a benchmark that captures the per-call allocation count for the validator's hot path. The hot path (validating a previously-seen type) must allocate ≤1 time. CI fails on regressions.

Comments