Step 1 of 4 · Reading · ~3 min
Learn
Generics and Reflection
Reflection
Reflection lets a function inspect and modify values whose type it does not know at compile time: list a struct's fields, read their tags, and set them. By the end you can write the kind of tag-driven loader encoding/json is built on.
In Interfaces a type switch opened an interface value's (type, value) pair for types you named in advance; reflect opens it for any type.
Type, Kind and Value
reflect.TypeOf(x) returns the dynamic type and reflect.ValueOf(x) the dynamic value. The Kind is the underlying category, and it differs from the type for every named type:
Branch on Kind in generic code: Celsius and float64 are different types but the same kind. Unnamed types such as []string have an empty Name.
Walking a struct and reading tags
A struct tag is the raw string after a field's type, by convention key:"value" pairs. Type.Field(k) returns a StructField with Name, Type, Tag, PkgPath (non-empty only for unexported fields) and Anonymous (true for an embedded field).
SKU string exported=true get="sku" lookup="sku",true
Cents int exported=true get="price_cents" lookup="price_cents",true
Stock int exported=true get="" lookup="",true
bin string exported=false get="" lookup="",false
Tag.Get returns "" both for db:"" (Stock) and for no db key (bin); only Lookup's second result tells them apart. The compiler does not check tags: a typo such as db:"sku is silently ignored.
Names and tags live on the Type; the current value lives on the Value: reflect.ValueOf(p).Field(k), and .Interface() turns it back into an interface{} (it panics on an unexported field).
Writing: the addressability rule
reflect.ValueOf(p) holds a copy of p. This is the addressability rule from pointer receivers, enforced with a panic:
CanSet reports whether a write would succeed: the value must be addressable and the field exported. Other misuse panics too: SetString on an int field gives call of reflect.Value.SetString on int Value, and calling Field on the pointer without Elem gives call of reflect.Value.Field on ptr Value. On this grader the pointer Kind is spelled reflect.Ptr.
Slices, maps, methods and DeepEqual
Len() and Index(i) walk a slice. MapKeys() returns keys in random order, so sort them before printing. MethodByName("Has") finds an exported method and Call([]reflect.Value{...}) invokes it; a missing name gives a Value whose IsValid() is false. reflect.DeepEqual compares recursively but strictly: DeepEqual([]int(nil), []int{}) and DeepEqual(int32(1), int64(1)) are both false.
The three laws: an interface value becomes a reflection object; a reflection object becomes an interface value (Interface()); to modify one, it must be settable. The costs: type errors become run-time panics, and every call is much slower than direct code. If the type is known at compile time, use generics.
Common mistakes
- Reflecting a copy.
reflect.ValueOf(cfg)then anySetpanics withunaddressable value; pass&cfgand callElem(). Tag.Getfor an optional tag. A missingminandmin:""both read as""; parsing that as0rejects-5on a field that has no lower bound. UseLookup.- Inferring "was it set" from the zero value. A field deliberately set to
0or""looks untouched. Record what was set separately.
Your exercise
Complete load and report so a Config is filled from key=value lines using the field tags and nothing else. The most likely mistake is storing a value before its range check, or deciding "required" from the zero value.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…