Fundamentals 27 min read

Understanding Go Reflection: Types, Values, and Practical Applications

Go’s reflection, accessed via the reflect package’s TypeOf and ValueOf functions, lets programs inspect and modify runtime types and values—including fields, tags, and slices—while obeying settable rules, enabling generic utilities such as JSON serialization, deep equality checks, and dynamic ORM-like behavior.

Didi Tech
Didi Tech
Didi Tech
Understanding Go Reflection: Types, Values, and Practical Applications

Go is a statically typed language, but its reflection mechanism allows programs to inspect and modify types and values at runtime, providing capabilities similar to dynamic languages.

All values in Go are stored in an interface{} which contains a pair of type and value . The empty interface (often called eface ) and the non‑empty interface ( iface ) have the same underlying layout: a pointer to a type descriptor and a pointer to the actual data.

Reflection is accessed through the reflect package, mainly via two functions:

reflect.TypeOf(i interface{}) reflect.Type returns a reflect.Type describing the dynamic type of i . The reflect.Type interface provides many methods such as Name() , Kind() , NumMethod() , Field(i int) , MethodByName(name string) , etc., which let you query every aspect of a type.

reflect.ValueOf(i interface{}) reflect.Value returns a reflect.Value that holds both the type information and a pointer to the actual data. reflect.Value offers methods to read and, when the value is settable, to modify the underlying data (e.g., SetInt , SetBool , SetFloat , Set , SetLen , SetCap , SetMapIndex ).

Three fundamental laws of Go reflection are:

Reflection converts an interface value to a reflection object ( reflect.Type / reflect.Value ).

Reflection can convert a reflection object back to an interface value via Value.Interface() .

To modify a reflected value, the reflect.Value must be settable (usually obtained by reflecting a pointer).

Example: attempting to change a plain variable via reflect.ValueOf(x).SetFloat(7.1) panics because the reflected value is not addressable. Using a pointer ( reflect.ValueOf(&x).Elem() ) makes the value settable.

A more elaborate example is the handsome function, which receives a slice of structs, searches for a field named Name (or with tag qson:"Name" ) equal to "qcrao" , and sets the Handsome boolean field to true . The function demonstrates:

Checking the kind of the reflected value ( reflect.Slice , reflect.Struct ).

Iterating over struct fields with Type.NumField() and Type.Field(i) .

Reading tags via StructField.Tag.Get("qson") .

Modifying exported fields with Value.FieldByName("Handsome").SetBool(true) .

Reflection can also read unexported fields (e.g., v.FieldByName("handsome") ) but cannot set them; attempting to call SetBool on an unexported field causes a panic.

Practical applications of reflection include JSON serialization ( json.Marshal , json.Unmarshal ) and deep equality checks ( reflect.DeepEqual ). Both functions rely on the same type/value introspection to walk arbitrary data structures.

reflect.DeepEqual first checks for nil, then compares the dynamic types, and finally recurses through the values. Special handling exists for maps, slices, pointers, functions, and floating‑point numbers. To avoid infinite loops on cyclic data structures, the implementation keeps a map of visited visit structs (pair of pointers and type) and treats already‑seen pairs as equal.

In summary, Go’s reflection system bridges static typing and dynamic inspection, enabling powerful generic code such as serializers, ORMs, and testing utilities while preserving type safety when used correctly.

goreflectionJSONtypeDeepEqualInterface@Value
Didi Tech
Written by

Didi Tech

Official Didi technology account

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.