← Files GophersARCHIVED FILE

skills/go-data-structures/references/containers-and-pointers.md

4.77 KB · Oct 3, 2026 · 06:31 UTC

↓ Download file

# container/*, Generic Wrappers, unsafe.Pointer, weak.Pointer

## `container/heap`

A min-heap. Implement `heap.Interface` (length, less, swap, push, pop) and use the package functions.

```go
type PQ []*Item

func (p PQ) Len() int            { return len(p) }
func (p PQ) Less(i, j int) bool  { return p[i].Priority < p[j].Priority }
func (p PQ) Swap(i, j int)       { p[i], p[j] = p[j], p[i] }
func (p *PQ) Push(x any)         { *p = append(*p, x.(*Item)) }
func (p *PQ) Pop() any {
    old := *p
    n := len(old)
    x := old[n-1]
    *p = old[:n-1]
    return x
}

pq := &PQ{}
heap.Init(pq)
heap.Push(pq, &Item{Priority: 3})
top := heap.Pop(pq).(*Item)
```

For a max-heap, reverse the `Less` comparison.

The `any` in `Push`/`Pop` is unfortunate — a generic wrapper is worth it in any non-trivial codebase.

## `container/list`

Doubly-linked list. Useful for LRU caches and workloads with frequent middle insertions/removals. Poor cache locality — every node is a heap allocation. Benchmark before choosing it over a slice.

```go
l := list.New()
e := l.PushBack(value)
l.Remove(e)
```

For most "list" needs, a slice with `slices.Delete` is faster.

## `container/ring`

Circular doubly-linked list with no head/tail. Useful for rolling windows and round-robin scheduling when the size is fixed.

```go
r := ring.New(5)
for i := 0; i < r.Len(); i++ {
    r.Value = i
    r = r.Next()
}
```

Niche but exactly right for its niche.

## Generic Wrappers

The `container/*` packages predate generics; their `any` API loses type safety. Wrap them.

```go
type Heap[T any] struct {
    less func(a, b T) bool
    data []T
}

// implement heap.Interface internally, expose typed Push/Pop
```

For sets and queues, generics make the API clean:

```go
type Set[T comparable] map[T]struct{}

func (s Set[T]) Add(v T)         { s[v] = struct{}{} }
func (s Set[T]) Has(v T) bool    { _, ok := s[v]; return ok }
func (s Set[T]) Remove(v T)      { delete(s, v) }
func (s Set[T]) Len() int        { return len(s) }
```

## Generic Constraints: Pick the Tightest

- `comparable` — for map keys and `==` use.
- `cmp.Ordered` (Go 1.21+) — `int`, `float`, `string`, anything `<`.
- Custom interface — for domain-specific ordering.

```go
func Min[T cmp.Ordered](s []T) T {
    m := s[0]
    for _, x := range s[1:] {
        if x < m { m = x }
    }
    return m
}
```

`any` should be the last resort — at that point you've given up most of what generics offer.

## Pointer Flavours

| Type | Use | Zero value |
|---|---|---|
| `*T` | normal indirection, mutation, optional | `nil` |
| `unsafe.Pointer` | FFI, low-level layout — six valid spec patterns only | `nil` |
| `weak.Pointer[T]` (Go 1.24+) | caches, canonicalisation | (no `nil`) |

## `unsafe.Pointer`

The six valid conversion patterns from the Go spec (paraphrased):

1. Conversion of `*T1` to `unsafe.Pointer` to `*T2`.
2. Conversion of `unsafe.Pointer` to `uintptr` (only for printing / system calls — see #4).
3. Conversion of `uintptr` to `unsafe.Pointer` — only when the `uintptr` was produced from a valid pointer that is still live.
4. Conversion of `unsafe.Pointer` to `uintptr` and back **in the same expression** for pointer arithmetic.
5. Conversion of `unsafe.Pointer` to `unsafe.SliceData`/`unsafe.StringData` results.
6. Conversion to `reflect.Value.UnsafePointer()` results.

The crucial rule: **never store a converted `uintptr` in a variable across statements**. The GC can move the underlying object between statements — the `uintptr` becomes a dangling reference with no way to know.

```go
// Bad — uintptr survives across statements; object may move
p := uintptr(unsafe.Pointer(&x))
doSomething()
*(*int)(unsafe.Pointer(p)) = 42

// Good — entirely within one expression
*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + offset)) = 42
```

Reach for `unsafe` only after profiling proves it's worth the cost.

## `weak.Pointer[T]` (Go 1.24+)

A weak reference does **not** prevent the GC from collecting the target. Use for caches and canonicalisation maps where holding entries forever would be a leak.

```go
import "weak"

type Cache struct {
    m map[string]weak.Pointer[Entry]
}

func (c *Cache) Get(k string) *Entry {
    if wp, ok := c.m[k]; ok {
        if e := wp.Value(); e != nil {
            return e
        }
    }
    return nil
}
```

When the strong reference goes away and GC runs, `wp.Value()` returns `nil`. The cache shrinks naturally without a custom eviction policy.

## Anti-Patterns

- `container/list` for an append-mostly workload — slice is faster.
- `any` in a generic API when a constraint would do.
- Storing a `uintptr` across statements.
- `unsafe.Pointer` "for performance" without a benchmark proving > 10% improvement on a verified hot path.
- A weak cache where every entry has a strong reference held elsewhere — the weakness does nothing.

SHA-256: 2a492f1c1e2805b95de4791691e7e78044640a573a26a46fe773d75c458d9f6c