Files
teamai-test/.teamai/skills/common/golang-data-structures/references/pointers.md
T

3.5 KiB

Pointer Types Deep Dive

Regular Pointers (*T)

Stack vs Heap (Escape Analysis)

Go's compiler decides whether to allocate on the stack or heap. A variable "escapes" to the heap when its lifetime extends beyond the function:

func noEscape() int {
    x := 42
    return x // x stays on stack — copied on return
}

func escapes() *int {
    x := 42
    return &x // x escapes to heap — pointer outlives function
}

Use go build -gcflags="-m" to see escape analysis decisions. Heap allocations add GC pressure — avoid unnecessary escapes in hot paths.

new(T) vs &T{}

Both allocate and return a pointer. &T{} is preferred because it allows field initialization:

p := new(Point)       // *Point with zero values
p := &Point{X: 1}     // *Point with initialized fields — preferred

unsafe.Pointer

unsafe.Pointer bypasses Go's type system for FFI and low-level memory manipulation. Only the 6 patterns from the Go spec are safe; any other pattern is undefined behavior.

The 6 Valid Patterns (from the Go spec)

These are the ONLY safe ways to use unsafe.Pointer. Any other pattern is undefined behavior.

Pattern 1: Convert *T to *U via unsafe.Pointer

// Reinterpret a float64 as its raw bits
f := 1.5
bits := *(*uint64)(unsafe.Pointer(&f))

Pattern 2: Convert unsafe.Pointer to uintptr and back (same expression)

// Pointer arithmetic — MUST be a single expression
p := unsafe.Pointer(uintptr(unsafe.Pointer(&s.field)) + offset)

Pattern 3: reflect.Value.Pointer() or UnsafeAddr() to unsafe.Pointer

p := unsafe.Pointer(reflect.ValueOf(&x).Pointer())

Pattern 4: syscall.Syscall arguments

syscall.Syscall(SYS_READ, fd, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))

Critical Rule: NEVER Store uintptr Across Statements

// ✗ DANGEROUS — GC can move the object between these two lines
u := uintptr(unsafe.Pointer(&x))
// ... GC may run here, moving x ...
p := unsafe.Pointer(u) // dangling pointer

// ✓ Safe — single expression
p := unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + offset)

Modern Alternatives (prefer these)

Function Since Purpose
unsafe.Add(ptr, len) Go 1.17 Pointer arithmetic without uintptr conversion
unsafe.Slice(ptr, len) Go 1.17 Create slice from pointer + length
unsafe.String(ptr, len) Go 1.20 Create string from pointer + length
unsafe.SliceData(s) Go 1.17 Get pointer to slice's backing array
unsafe.StringData(s) Go 1.20 Get pointer to string's backing array

These are safer than manual uintptr arithmetic because they keep values as pointers (visible to GC) throughout.

weak.Pointer[T] (Go 1.24+)

A weak pointer holds a reference to an object without preventing garbage collection. When the GC reclaims the object, Value() returns nil.

strong := new(MyType)
w := weak.Make(strong)

if p := w.Value(); p != nil {
    // object still alive
} else {
    // object was garbage collected
}

Use Cases

  • Deduplication caches — intern equivalent values without preventing GC
  • Automatic cache eviction — cached objects evict when no strong references remain

runtime.AddCleanup vs runtime.SetFinalizer

Prefer runtime.AddCleanup (Go 1.24+) over runtime.SetFinalizer:

  • Multiple cleanups can be registered per object
  • Cleanup function receives a value, not a pointer to the collected object
  • No risk of resurrecting the object
  • Works correctly with weak pointers