Memory: the compiler errors¶
Level: 301 · anyone whose &, array length or unsafe call was refused
One line: Six refusals that come from how Go lays memory out — a map element has no address because the map may move it, an array's length is part of its type, unsafe.Sizeof of a type parameter's value is not a constant, Offsetof wants a selector, and a uintptr is an integer — each recorded as the go1.25 compiler prints it.
cannot take address of¶
The reader wanted a pointer to a struct held in a map, to the result of a call, and to a constant:
Verified output of memory_err_address_of_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build addresses.go
# command-line-arguments
./addresses.go:9:14: invalid operation: cannot take address of orders["a-1"] (map index expression of struct type Order)
./addresses.go:10:15: invalid operation: cannot take address of newest() (value of struct type Order)
./addresses.go:11:16: invalid operation: cannot take address of 5 (untyped int constant)
exit status 1
The mistake. A map element is not addressable: the runtime may move every element when the map grows, so a pointer into it could dangle, and the spec ↗ allows & only on addressable operands, composite literals, and the results of *. A call's result and a constant have no home the pointer could name.
The fix. Store pointers in the map, or take the value out, change it, and put it back; give a result or a constant a variable first.
orders := map[string]*Order{"a-1": {Qty: 2}}
fromMap := orders["a-1"] // already a pointer
latest := newest()
fromCall := &latest
five := 5
fromConst := &five
cannot assign to struct field in map¶
The reader tried to change one field of a struct stored in a map, in place:
Verified output of memory_err_map_field_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build map_field.go
# command-line-arguments
./map_field.go:7:2: cannot assign to struct field orders["a-1"].Qty in map
exit status 1
The mistake. The same rule from the other side: orders["a-1"].Qty = 3 would need the address of the element, and there is none. The message names the whole selector.
The fix. Read, modify, write back — or store pointers.
invalid array length¶
The reader sized an array from a variable, and another from a negative number:
Verified output of memory_err_array_length_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build lengths.go
# command-line-arguments
./lengths.go:7:16: invalid array length slots
./lengths.go:8:16: invalid array length -1 (untyped int constant)
exit status 1
The mistake. [4]int and [5]int are different types, so the length must be known when the type is: the spec ↗ requires a constant expression representable by an int, and a variable is not one. A slice is the type whose length is decided at run time.
The fix. make([]int, slots) for a run-time length; a named constant for a fixed one.
array length unsafe.Sizeof(v) must be constant¶
The reader used unsafe.Sizeof of a value of type T, inside a generic function, as an array length:
Verified output of memory_err_sizeof_type_param_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build encode.go
# command-line-arguments
./encode.go:15:11: array length unsafe.Sizeof(v) (value of type uintptr) must be constant
exit status 1
The mistake. unsafe.Sizeof(Reading{}) is the constant 16, and rawReading compiled. Inside encode[T any], v's size depends on the type argument, so the spec ↗ makes Sizeof(v) a non-constant value of type uintptr, and the array declaration is the line that fails. Field order decides a struct's size uses the constant form; A type parameter needs a constraint is where the type sets come from.
The fix. Use a slice, sized at run time.
is not a selector expression / embedded via a pointer¶
The reader asked unsafe.Offsetof about a variable, and about a field reached through an embedded pointer:
Verified output of memory_err_offsetof_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build offsets.go
# command-line-arguments
./offsets.go:24:30: invalid argument: o is not a selector expression
./offsets.go:25:30: invalid argument: field Qty is embedded via a pointer in Line
exit status 1
The mistake. Offsetof reports where a field sits inside its struct, so its argument must be a selector x.f naming a field of x's struct. o alone has no offset. l.Qty is a selector, but Qty is a field of the Order that Line embeds by pointer, so it lives in a different allocation and no offset inside Line describes it; the spec ↗ requires that the field not be reached through embedded pointers.
The fix. Ask about the outer struct's own fields, and go through the pointer yourself for the rest.
fmt.Println(unsafe.Offsetof(l.Order)) // the pointer field of Line
fmt.Println(unsafe.Offsetof(l.Order.Qty)) // a field of the Order it points at
cannot convert addr (variable of type uintptr) to type *Order¶
The reader turned a pointer into a uintptr and tried to turn it back:
Verified output of memory_err_uintptr_to_pointer_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
$ go build integer_pointer.go
# command-line-arguments
./integer_pointer.go:13:19: cannot convert addr (variable of type uintptr) to type *Order
exit status 1
The mistake. A uintptr is an integer with no meaning to the collector: it does not keep an object alive, and it is not updated when a stack is copied (A goroutine stack grows and has a limit). The only conversion the language allows is uintptr → unsafe.Pointer → *T, and unsafe.Pointer's rules ↗ allow even that only in the same expression that made the integer.
The fix. Do the arithmetic in one expression, or use unsafe.Add; go vet's unsafeptr check on Memory: what go vet and gofmt catch flags the two-step version. unsafe.Pointer reinterprets memory is the lesson on what the conversion means.
back := (*Order)(unsafe.Pointer(&o))
qty := (*int)(unsafe.Add(unsafe.Pointer(&o), unsafe.Offsetof(o.Qty)))
The six drivers
memory_err_address_of_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# Three things that have no address: a map element (the map may move it), a
# call's result, and a constant. The compiler refuses & on each of them.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/addresses.go" <<'GO'
package main
type Order struct{ Qty int }
func newest() Order { return Order{Qty: 1} }
func main() {
orders := map[string]Order{"a-1": {Qty: 2}}
fromMap := &orders["a-1"]
fromCall := &newest()
fromConst := &5
_, _, _ = fromMap, fromCall, fromConst
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build addresses.go'
(cd "$dir" && go build addresses.go) 2>&1
echo "exit status $?"
memory_err_map_field_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# A map element is not addressable, so a field of a struct held in a map
# cannot be assigned in place.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/map_field.go" <<'GO'
package main
type Order struct{ Qty int }
func main() {
orders := map[string]Order{"a-1": {Qty: 2}}
orders["a-1"].Qty = 3
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build map_field.go'
(cd "$dir" && go build map_field.go) 2>&1
echo "exit status $?"
memory_err_array_length_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# An array's length is part of its type, so it must be a constant: not a
# variable, and not negative.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/lengths.go" <<'GO'
package main
import "fmt"
func main() {
slots := 4
var perTable [slots]int
var negative [-1]int
fmt.Println(slots, perTable, negative)
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build lengths.go'
(cd "$dir" && go build lengths.go) 2>&1
echo "exit status $?"
memory_err_sizeof_type_param_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# unsafe.Sizeof is a constant for every ordinary type, and so it can size an
# array. For a value of a type parameter's type the size depends on the type
# argument, so Sizeof is not a constant and the array declaration fails.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/encode.go" <<'GO'
package main
import "unsafe"
type Reading struct {
Sensor int32
Value float64
}
// rawReading is fine: Sizeof(Reading{}) is the constant 16.
var rawReading [unsafe.Sizeof(Reading{})]byte
// encode is not: Sizeof(v) is only known once T is.
func encode[T any](v T) []byte {
var raw [unsafe.Sizeof(v)]byte
return raw[:]
}
func main() {
_ = rawReading
_ = encode(Reading{})
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build encode.go'
(cd "$dir" && go build encode.go) 2>&1
echo "exit status $?"
memory_err_offsetof_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# unsafe.Offsetof takes a selector x.f naming a field of a struct. A plain
# variable is not a selector, and a field reached through an embedded
# pointer is not in the outer struct's memory at all.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/offsets.go" <<'GO'
package main
import (
"fmt"
"unsafe"
)
type Order struct {
ID int
Qty int
}
// Line embeds a pointer to its Order, so Order's fields live elsewhere.
type Line struct {
*Order
Discount int
}
func main() {
o := Order{ID: 1, Qty: 2}
l := Line{Order: &o}
fmt.Println(unsafe.Offsetof(o.Qty)) // fine: a field of o
fmt.Println(unsafe.Offsetof(l.Discount)) // fine: a field of l
fmt.Println(unsafe.Offsetof(o))
fmt.Println(unsafe.Offsetof(l.Qty))
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build offsets.go'
(cd "$dir" && go build offsets.go) 2>&1
echo "exit status $?"
memory_err_uintptr_to_pointer_sh.sh in full — pasted here by tools/run_examples.py from the file CI runs.
#!/usr/bin/env bash
# A uintptr is an integer, not a pointer: the compiler will not convert it
# to *T directly. The only route is through unsafe.Pointer, and go vet's
# unsafeptr check has an opinion about that route too.
set -u
export GOTOOLCHAIN=local
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
cat >"$dir/integer_pointer.go" <<'GO'
package main
import (
"fmt"
"unsafe"
)
type Order struct{ Qty int }
func main() {
o := Order{Qty: 2}
addr := uintptr(unsafe.Pointer(&o))
back := (*Order)(addr)
fmt.Println(back.Qty)
}
GO
say() { printf '$ %s\n' "$*"; }
say 'go build integer_pointer.go'
(cd "$dir" && go build integer_pointer.go) 2>&1
echo "exit status $?"
Sources¶
- The Go specification: Address operators ↗, Array types ↗, Package unsafe ↗ and Assignment statements ↗.
unsafe.Pointer↗ — the six valid patterns.- The messages:
cmd/compile/internal/types2at go1.25.5 ↗, the type checker that prints them.