Why does reflect.Value.String() on a Value holding an int return a placeholder instead of panicking?
answer
- most accessors are gated by category
- one of them refuses to panic
- printing must never crash the program
- the result looks like angle brackets
- silent wrong output, not a crash
basics
~20 sString is the one typed accessor that never panics on the wrong Kind, because reflect.Value must satisfy the Stringer convention and be printable. On a non-string value it returns a form like <int Value>. Int, Float and Bool panic instead.
solid answer
~40 sEvery other typed accessor on `reflect.Value` — `Int()`, `Uint()`, `Float()`, `Bool()`, `Bytes()`, `Len()` — panics when the value's `Kind` does not match, which is why `Kind` is the gate you check first. `String()` is deliberately special-cased: Go's convention is that a `String() string` method makes a type printable, and `reflect.Value` must be safe to print, so instead of panicking on a non-string it returns a placeholder of the form `"<int Value>"`. The practical hazard is that this fails *silently*: code that builds output from `v.String()` on arbitrary values emits `<int Value>` text instead of crashing where the bug is. Render through `fmt.Sprint(v.Interface())` instead, or check `Kind() == reflect.String` first. Note also that `fmt` itself special-cases a `reflect.Value` operand and prints the value it holds, so `fmt.Println(v)` prints `42` while `v.String()` does not.
code
go · 8 linesv := reflect.ValueOf(42)
fmt.Println(v.String()) // <int Value>
fmt.Println(v) // 42 (fmt replaces a reflect.Value operand)
fmt.Println(v.Int()) // 42
// v.Float() panics:
// reflect: call of reflect.Value.Float on int Valuego deeper
Remember that reading data out of a reflect.Value only works when the value's category matches the accessor, and that String is the odd one that returns a placeholder instead of failing.
Explain why the exception exists — a printable type must never crash the printer — and which accessors are strict, including that Int refuses unsigned values.
Point at the silent-failure mode: rendering code built on Value.String emits plausible placeholder text instead of failing, so route through Interface or gate on Kind and cover it with a test.
Weigh whether helpers your teams share should be total and forgiving like this method, or strict and loud, and be able to defend the debuggability cost either choice imposes.
## The rule, and its one exception `reflect.Value` exposes typed accessors that read the underlying data: `Int() int64`, `Uint() uint64`, `Float() float64`, `Complex() complex128`, `Bool() bool`, `Bytes() []byte`, `Len() int`, `String() string`. Each is only meaningful for certain `Kind`s, and the package's rule is blunt: call one on the wrong `Kind` and it panics, with a message like `reflect: call of reflect.Value.Float on int Value`. That is why reflective code is written as a `Kind` switch first, accessor second. `String()` is the exception, and the documentation says so explicitly. On a `Kind` `String` value it returns the string. On anything else it does **not** panic — it returns a placeholder of the form `"<int Value>"`, `"<[]int Value>"`, `"<invalid Value>"` for the zero `Value`. ## Why the exception exists Go has a pervasive convention: a type with a `String() string` method is a `fmt.Stringer`, and printing machinery calls that method. `reflect.Value` needs to be printable — it turns up in log lines, in test failure output, in `%v` verbs inside the standard library itself. If `String()` panicked whenever the wrapped value was not a string, then printing a `reflect.Value` during debugging would crash the program precisely when someone was trying to work out what was wrong. So the method was made total: it always returns something. ## Why that is a hazard The cost of a method that never fails is that a mistake produces plausible-looking output instead of an error. A helper that renders arbitrary values with `v.String()` will happily emit ``` user id: <int Value> ``` and nothing anywhere reports a problem. The value was there, the type was right, the code just asked the wrong question. Compare `v.Int()`, which would have stopped at the exact line. The fixes are small: - If you want the data as a Go value, use `v.Interface()` and let `fmt` format it: `fmt.Sprint(v.Interface())`. - If you want to print the `reflect.Value` directly, just do it. `fmt` special-cases a `reflect.Value` operand by replacing it with the concrete value it holds and continuing, so `fmt.Println(v)` on a `Value` wrapping `42` prints `42`, not `<int Value>`. This surprises people in the opposite direction: `fmt.Println(v)` and `fmt.Println(v.String())` print different things. - If you really want the string only when it is a string, gate on `v.Kind() == reflect.String`. ## The accessors that do panic, and how to pre-check The rest of the family follows the strict rule, and the mapping is worth knowing: - `Int()` covers `Int`, `Int8`, `Int16`, `Int32`, `Int64` and returns `int64`. It does **not** accept unsigned kinds — `Uint()` is separate, and returns `uint64`. Assuming `Int()` handles every integer is a common bug when walking arbitrary structs. - `Float()` covers `Float32` and `Float64`; `Complex()` covers the two complex kinds. - `Len()` covers `Array`, `Chan`, `Map`, `Slice` and `String`. - `Bytes()` requires a slice of bytes (or an addressable byte array). - `IsNil()` requires one of the nilable kinds: `Chan`, `Func`, `Interface`, `Map`, `Pointer`, `Slice`. Rather than a bare `Kind` switch you can use the `CanInt`, `CanUint`, `CanFloat` and `CanComplex` predicates, which report whether the corresponding accessor is legal. They read better in a numeric normaliser than enumerating five integer kinds by hand: ```go switch { case v.CanInt(): n = float64(v.Int()) case v.CanUint(): n = float64(v.Uint()) case v.CanFloat(): n = v.Float() default: return fmt.Errorf("not numeric: %s", v.Kind()) } ``` ## What an interviewer is listening for Two things. First, the general rule — accessors are `Kind`-gated and panic otherwise — because that is the discipline all reflective code is written under. Second, the awareness that `String()` breaks the rule for a *reason* (printability) and that the reason creates a silent-failure mode in rendering code. A candidate who has actually shipped reflective code usually volunteers the third fact unprompted: `fmt` unwraps a `reflect.Value` operand, so printing the `Value` and calling its `String()` method give different answers.
- Which reflect.Value methods must you gate on Kind before calling?All the typed readers: `Int`, `Uint`, `Float`, `Complex`, `Bool`, `Bytes`, `Len`, `Index`, `MapKeys`, `NumField` and `IsNil` each panic on a mismatched `Kind`. Use a `Kind` switch, or the `CanInt`/`CanUint`/`CanFloat`/`CanComplex` predicates for the numeric family, before reading.
- Does Value.Int() work for a value whose Kind is Uint64?No — it panics. `Int()` covers only the signed kinds and returns `int64`; unsigned values need `Uint()`, which returns `uint64`. A normaliser that assumes `Int()` handles every integer crashes the first time it meets a `uint64` field, which is why `CanInt` and `CanUint` are separate predicates.
- How do you print the data a reflect.Value holds, the way fmt would print the original?Either pass the `reflect.Value` straight to `fmt` — it replaces a `reflect.Value` operand with the concrete value it holds — or call `fmt.Sprint(v.Interface())` when you need the `any` for other reasons. What you must not do is build output from `v.String()`, which silently yields a placeholder for non-strings.
saying these in an interview costs you the question
- Expects String() to panic like Int() does
- Renders arbitrary values with Value.String()
- Assumes Int() also reads unsigned values
- Thinks fmt prints <int Value> for a reflect.Value
- Calls a typed accessor without checking Kind