In Go's reflect package, how does reflect.Zero(t) differ from Value.IsZero()?
answer
- one is a constructor, one is a predicate
- the constructed one is deliberately read-only
- comparing via interfaces has a runtime trap
- uncomparable dynamic types panic on ==
- for a writable blank you need New plus Elem
basics
~20 sreflect.Zero(t) constructs a value: the zero value of type t, explicitly not addressable and not settable. Value.IsZero() inspects a value and reports whether it already equals the zero value of its own type. One builds, one asks.
solid answer
~50 s`reflect.Zero(t)` is a constructor. It returns a `reflect.Value` holding the zero value of `t` — `0`, `""`, `nil` for reference kinds, a zeroed struct — and the documentation is explicit that the result is neither addressable nor settable, so it is a blank to read or pass along, never something to fill in. `Value.IsZero()` is a predicate on a value you already have: it reports whether that value is the zero value for its type, recursing into struct fields. The practical reason to prefer `IsZero` over comparing against `reflect.Zero` is that the comparison `v.Interface() == reflect.Zero(v.Type()).Interface()` panics at runtime when the dynamic type is uncomparable — a slice, a map, a function — while `IsZero` handles every kind. When you need a blank you can actually write into, the answer is neither of these: it is `reflect.New(t).Elem()`.
code
go · 6 linesz := reflect.Zero(reflect.TypeOf(0)) // read-only: not addressable, not settable
fmt.Println(z.IsZero()) // prints: true
v := reflect.New(reflect.TypeOf(0)).Elem() // allocated, writable
v.SetInt(7)
fmt.Println(v.IsZero()) // prints: falsego deeper
Keep the two roles straight: reflect.Zero(t) makes a blank value of type t, Value.IsZero() asks whether a value you already hold is still blank. Remember the blank from Zero cannot be written to.
Explain IsZero kind by kind, especially that reference kinds are zero only when nil, and why comparing through Interface() risks a runtime panic on slices, maps and funcs that the compiler cannot warn about.
Show the judgment in a walker: IsZero decides whether a caller already supplied a field, Len decides whether a container is empty, and confusing the two silently overwrites data or skips fields that needed filling.
Own what blank means in your library's contract — untouched, nil, or empty — because callers build conditional logic on it, and a builder that changes its answer later breaks fixtures no one can trace back to you.
## Two functions that sound alike and do opposite jobs `reflect.Zero` **produces** a value. `Value.IsZero` **asks a question about** a value. The names are close enough that they get swapped in review, so it is worth being precise about each. ## reflect.Zero(t) `func Zero(typ Type) Value` returns a `reflect.Value` representing the zero value for `typ`. For `int` that is `0`; for `string`, `""`; for a pointer, slice, map, channel, function or interface, `nil`; for a struct or array, every element zeroed recursively. The critical property, stated in the package documentation, is that **the returned Value is neither addressable nor settable**. It is a read-only handle. That makes it useful for three things: - as a placeholder to pass where a `reflect.Value` argument is required — for instance filling in a missing argument, or clearing a field by `Set`ting it to the zero of its type - as a comparison target, where the type is comparable - as the result of a lookup that found nothing And it makes it useless for one thing people constantly try: building a value to fill in. `reflect.Zero(t)` then `Set` on it panics, because there is no memory behind it to write to. The tool for that is `reflect.New(t).Elem()`, which allocates real storage and hands back an addressable handle. Note also that `reflect.Zero` costs nothing for small types — it does not necessarily allocate, since it can hand back a read-only view of a shared zeroed region — whereas `reflect.New` always allocates. ## Value.IsZero() `func (v Value) IsZero() bool` reports whether `v` is the zero value for its type. It is kind-aware: - numeric kinds compare against zero - `string` compares against the empty string - pointer, slice, map, channel, func and interface kinds are zero when they are `nil` - arrays and structs are zero when **every** element or field is zero, checked recursively It panics only in one case: when `v` is the zero `reflect.Value` itself — an invalid Value, `Kind() == reflect.Invalid`, such as the one returned by `reflect.ValueOf(nil)` or by a failed lookup. Guard with `v.IsValid()` when the Value came from a `MapIndex` or a `FieldByName` that might have missed. ## Why not just compare against reflect.Zero? The tempting one-liner is: ``` if v.Interface() == reflect.Zero(v.Type()).Interface() { ... } ``` It works for `int`, `string` and comparable structs. It **panics at runtime** the moment `v`'s dynamic type is uncomparable — a slice, a map, or a function — because comparing two interface values with uncomparable dynamic types is a runtime error in Go, not a compile-time one. The compiler cannot see it coming because both sides are `any`. `IsZero` has no such hole. It is also cheaper: no boxing into interfaces, no allocation. ## Nil versus empty, one more time `IsZero` reports **nil**, not **empty**. A map allocated with `reflect.MakeMap` and never written to has `Len() == 0` but `IsZero() == false`, because the zero value of a map type is nil and this map is not nil. The same holds for a slice from `reflect.MakeSlice(t, 0, 8)`. If what you actually want to know is "does this container hold anything", ask `Len() == 0`; if you want to know "was this field ever populated", `IsZero` is the right question — those are genuinely different, and choosing the wrong one produces a builder that either re-allocates fields it already made or skips fields it should fill. ## In a builder The pattern that uses both correctly: walk the target's fields, and for each one, use `IsZero` to decide whether the caller already supplied something (leave it alone) or not (fill it in). When filling it in, allocate with `reflect.New(fieldType).Elem()` — never with `reflect.Zero(fieldType)`, which cannot be written to — and when the value is complete, `Set` it into the field. Reserve `reflect.Zero` for the case where the builder needs to *clear* a field back to its blank state, which is the one job it is exactly right for.
- When does Value.IsZero() panic?Only on an invalid Value — one whose `Kind()` is `reflect.Invalid`, such as the result of `reflect.ValueOf(nil)`, a `MapIndex` for a key that is absent, or a `FieldByName` that matched nothing. Every real kind has a defined zero and is handled. Guard the uncertain cases with `v.IsValid()` before asking `IsZero()`.
- Does IsZero report true for a map allocated with reflect.MakeMap that has no entries?No. The zero value of a map type is nil, and that map is not nil, so `IsZero()` is false even though `Len()` is 0. The same applies to a slice from `reflect.MakeSlice(t, 0, 8)`. If the question you actually mean is "is it empty", ask `Len() == 0`; `IsZero` answers "is it still the blank the type starts with".
- You need a blank value of type t that a builder will then populate. Which call do you use?`reflect.New(t).Elem()`. It allocates real storage, so the resulting Value is addressable and can be written into. `reflect.Zero(t)` produces the same *contents* but is documented as neither addressable nor settable, so any attempt to fill it in panics. Reach for `Zero` only when you want a read-only blank to compare against or to clear a field back to.
saying these in an interview costs you the question
- Tries to Set into the result of reflect.Zero
- Compares uncomparable types through Interface() and ==
- Thinks IsZero means the container is empty
- Believes reflect.Zero allocates like reflect.New
- Calls IsZero on an invalid Value without checking IsValid