Skip to content

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.

order := orders["a-1"]
order.Qty = 3
orders["a-1"] = order

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.

perTable := make([]int, slots)
const seats = 4
var fixed [seats]int

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.

func encode[T any](v T) []byte {
    raw := make([]byte, unsafe.Sizeof(v))
    return raw
}

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