Reflection and unsafe: the compiler errors¶
Level: 301 · anyone whose first unsafe.Pointer or reflect.Value did not compile — and whose second one compiled and panicked
One line: The compiler refuses the pointer conversions that skip unsafe.Pointer, a reflect.Value used as if it were the value it describes, and unsafe.Sizeof on something that is not an expression; everything about which Kind a Value holds is known only at run time, so those mistakes are panics, recorded here with their exact messages.
cannot convert &x (value of type int) to type float64¶
Reading an int's bytes as a float64 by converting the pointer directly.
Verified output of reflection_err_pointer_convert_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build bits.go
# command-line-arguments
./bits.go:7:24: cannot convert &x (value of type *int) to type *float64
exit status 1
The mistake — Go has no cast: a conversion between pointer types is allowed only when the underlying base types are identical (Conversions ↗), and int and float64 are not.
The fix — decide which you meant. A new value: float64(x). The same bytes read as another type: pattern 1 of unsafe.Pointer reinterprets memory, or better math.Float64frombits(uint64(x)), which says so.
asValue := float64(x) // 1 -> 1.0
asBits := *(*float64)(unsafe.Pointer(&x)) // the bytes of 1 read as a float64: 5e-324
cannot indirect p (variable of type unsafe.Pointer)¶
Dereferencing an unsafe.Pointer, and converting an int to one.
Verified output of reflection_err_indirect_unsafe_pointer_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build deref.go
# command-line-arguments
./deref.go:11:15: invalid operation: cannot indirect p (variable of type unsafe.Pointer)
./deref.go:12:22: cannot convert total (variable of type int) to type unsafe.Pointer
exit status 1
The mistake — unsafe.Pointer points at memory of no particular type, so *p has no type to be; and unsafe.Pointer(total) fails because an int is not an address — only a pointer or a uintptr converts to Pointer.
The fix — convert to a typed pointer before dereferencing; and if you really hold an address as an integer, it has to be a uintptr, which is the round trip that go vet flags.
reflect.Value is not a function¶
Calling a method found by name with parentheses, as if MethodByName returned a Go function.
Verified output of reflection_err_call_non_function_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build method.go
# command-line-arguments
./method.go:14:11: invalid operation: cannot call reflect.ValueOf(inv).MethodByName("Total") (value of struct type reflect.Value): reflect.Value is not a function
exit status 1
The mistake — MethodByName returns a reflect.Value of Kind Func; a Value is a struct, and the call syntax needs a function type.
The fix — Call takes and returns []reflect.Value; or assert the Value to a function type first.
results := reflect.ValueOf(inv).MethodByName("Total").Call(nil)
fmt.Println(results[0].Float())
total := reflect.ValueOf(inv).MethodByName("Total").Interface().(func() float64)
fmt.Println(total())
cannot use quantity (variable of struct type reflect.Value) as int value¶
Assigning a reflect.Value to an int, and comparing one with 3.
Verified output of reflection_err_value_is_not_the_value_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build value.go
# command-line-arguments
./value.go:10:14: cannot use quantity (variable of struct type reflect.Value) as int value in variable declaration
./value.go:11:29: cannot convert 3 (untyped int constant) to type struct{typ_ *abi.Type; ptr unsafe.Pointer; reflect.flag}
exit status 1
The mistake — the Value describes the int; it is not the int. The second message shows what it is: a struct of a type pointer, a data pointer and a flag word.
The fix — read the value out with the method for its Kind, or go back through an interface value with Interface() and a type assertion, or with Go 1.25's TypeAssert.
use of untyped nil in argument to unsafe.Sizeof¶
Asking for the size of nil, of a type name, and of a call that returns nothing.
Verified output of reflection_err_sizeof_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build sizes.go
# command-line-arguments
./sizes.go:11:28: use of untyped nil in argument to unsafe.Sizeof
./sizes.go:12:28: int64 (type) is not an expression
./sizes.go:13:28: audit() (no value) used as value
exit status 1
The mistake — Sizeof takes an expression of any type (Package unsafe ↗); an untyped nil has no type, int64 is a type rather than an expression, and audit() has no value.
The fix — give it a typed value: a zero of the type, or a typed nil pointer.
fmt.Println(unsafe.Sizeof(int64(0))) // 8
fmt.Println(unsafe.Sizeof((*int)(nil))) // 8: the size of a pointer
fmt.Println(unsafe.Sizeof(audit)) // 8: a func value is a pointer
invalid argument: packet (variable of type []byte) is not a pointer¶
Passing a slice to unsafe.Slice, which wants a pointer to the first element and a length.
Verified output of reflection_err_slice_needs_pointer_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build view.go
# command-line-arguments
./view.go:10:23: invalid argument: packet (variable of type []byte) is not a pointer
exit status 1
The mistake — unsafe.Slice(ptr, len) builds a slice from a pointer; a slice already is one, and re-slicing it is ordinary Go.
The fix — packet[:2]; or, when the point is the pointer, unsafe.SliceData(packet) is the *byte that unsafe.Slice and unsafe.String take.
The panics the compiler cannot see¶
A reflect.Value is one static type, whatever Kind it holds, so Field on an int, Int on a string, Elem on a non-pointer and an index past NumField all compile. Each is a panic from package reflect, and the last one — reflect.TypeOf(nil) returns a nil Type, and a method on a nil interface is a nil-pointer dereference — comes from the runtime. reflect.ValueOf(nil) is different: it returns the zero Value, whose Kind is Invalid and whose Type panics.
Verified output of reflection_err_runtime_panics_go.go — regenerated by tools/run_examples.py, never hand-typed.
Field(0) on an int panic: reflect: call of reflect.Value.Field on int Value
Field(2) on a 2-field struct panic: reflect: Field index out of range
Int() on a string panic: reflect: call of reflect.Value.Int on string Value
NumField() on an int Type panic: reflect: NumField of non-struct type int
Elem() on an int panic: reflect: call of reflect.Value.Elem on int Value
Elem() on an int Type panic: reflect: Elem of invalid type int
Call() on an int panic: reflect: call of reflect.Value.Call on int Value
Type() on the zero Value panic: reflect: call of reflect.Value.Type on zero Value
Kind() on the zero Value no panic: Kind is invalid
TypeOf(nil) is nil, so .Kind() panic: runtime error: invalid memory address or nil pointer dereference
reflection_err_runtime_panics_go.go
reflection_err_runtime_panics_go.go in full — pasted here by tools/run_examples.py from the file CI runs.
// The compiler checks that a reflect.Value is a reflect.Value; what Kind it
// holds is known only at run time, so a method called on the wrong Kind, or
// a Field index past the end, is a panic. Each one here is recovered and
// printed. Every message is from package reflect itself, except the last.
//
// go build reflection_err_runtime_panics_go.go && ./reflection_err_runtime_panics_go
package main
import (
"fmt"
"reflect"
)
type Invoice struct {
Number string
Amount float64
}
func main() {
count := 3
inv := Invoice{Number: "INV-7", Amount: 12.5}
try("Field(0) on an int", func() { reflect.ValueOf(count).Field(0) })
try("Field(2) on a 2-field struct", func() { reflect.ValueOf(inv).Field(2) })
try("Int() on a string", func() { reflect.ValueOf(inv.Number).Int() })
try("NumField() on an int Type", func() { reflect.TypeOf(count).NumField() })
try("Elem() on an int", func() { reflect.ValueOf(count).Elem() })
try("Elem() on an int Type", func() { reflect.TypeOf(count).Elem() })
try("Call() on an int", func() { reflect.ValueOf(count).Call(nil) })
try("Type() on the zero Value", func() { reflect.ValueOf(nil).Type() })
try("Kind() on the zero Value", func() {
fmt.Printf("%-32s no panic: Kind is %s\n", "Kind() on the zero Value", reflect.ValueOf(nil).Kind())
})
try("TypeOf(nil) is nil, so .Kind()", func() { reflect.TypeOf(nil).Kind() })
}
// try runs f and prints what it panicked with.
func try(what string, f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%-32s panic: %v\n", what, r)
}
}()
f()
}
The mistake — trusting the Kind of a Value that came from an any. The messages name the method and the Kind (reflect: call of reflect.Value.Field on int Value), which is the compiler's error message arriving late.
The fix — check Kind() (and IsValid() for a value that may be nil) before a Kind-specific method, and return an error; reflect cannot know what you expected, but your function can. The settability panics — using unaddressable value, using value obtained using unexported field, value of type string is not assignable to type int — are on reflect sets only addressable values.
v := reflect.ValueOf(x)
if !v.IsValid() || v.Kind() != reflect.Struct {
return fmt.Errorf("want a struct, got %v", v.Kind())
}
A malformed struct tag is not a compiler error¶
json:name, json: "name" and json:"a",xml:"b" are all valid Go: a tag is a string literal. reflect.StructTag.Get returns "" for the key, encoding/json falls back to the field name, and nothing fails. go vet's structtag analyzer is the only check, on the vet page.
In other languages¶
- Rust turns most of this page into compile errors: a raw pointer cast between unrelated types is
aswith a size check intransmute, a private field is unreachable at compile time rather than at run time, and there is noValueto call the wrong method on — Whatunsafeturns off ↗ lists what stays checked insideunsafe. - C compiles the first entry without a word:
*(double *)&xis a cast, and reading it is undefined behaviour the compiler may assume never happens.
Sources¶
- The Go specification: Conversions ↗, Package unsafe ↗, Calls ↗.
reflect.Value↗ — the sentence in the type's documentation that a method inappropriate to the kind of type causes a run time panic.src/reflect/value.goat go1.25.5 ↗ andsrc/reflect/type.go↗ —ValueError,mustBe,NumFieldandElemand their messages.unsafe.Slice↗,unsafe.SliceData↗,unsafe.Sizeof↗.