Session
Weak Pointers in Go: Implementation Deep Dive
Go's weak pointer implementation looks deceptively simple: 'Make' and 'Value', that's it. But underneath lies an elegant design involving indirection objects, a handle table, careful GC integration, and an equality guarantee that no other major language provides. How does 'wp1 == wp2' remain true after the object is collected? Why is the indirection object exactly 8 bytes? How does the GC know when to zero the pointer without causing races?
This talk dissects the implementation: the indirection layer that makes equality stable, the handle table that enables efficient lookups, the GC write barrier integration, and why generics were required for a type-safe API. We'll read the actual runtime code and understand the design constraints that shaped each decision.
Main idea: Go's weak pointer uses an 8-byte indirection object between the weak pointer and the target. The weak pointer holds a pointer to this indirection object (not to the target). When the target dies, the GC zeros the indirection object's pointer field—but the indirection object itself survives. This is why 'wp1 == wp2' stays true after collection: both still point to the same indirection object.
Questions this talk answers:
- Why the indirection layer? Without it, you have a race: the GC wants to collect the object while user code reads the weak pointer. With indirection, the GC atomically zeros one pointer (in the indirection object), and user code atomically reads it. No races, no locks needed on the hot path.
- How does the handle table work? Creating a weak pointer for the same object twice must return equivalent weak pointers (same indirection object). The runtime maintains a table mapping objects to their indirection objects. This uses the object's address as a key, with careful handling when objects move during GC.
- Why is generics required? Before Go 1.18, a type-safe weak pointer API would need one function per type or use 'interface{}' everywhere. Generics enable 'weak.Pointer[T]' where 'Value()' returns '*T' without casts. The implementation uses unsafe internally but presents a safe API.
- How does the GC integration work? The GC must zero indirection objects for dead targets but not follow weak pointers as roots. This requires special handling in the mark phase: indirection objects are tracked separately, and their target pointers are processed in a second pass after determining liveness.
- What makes the equality guarantee possible? Indirection objects are never freed while any weak pointer references them. They form a secondary lifetime: the target can die, but the indirection object persists until all weak pointers to it are gone. This is tracked via reference counting on the indirection objects.
Attendees will understand the implementation well enough to read 'src/runtime/weak.go' themselves, know why each design decision was made (indirection for races, handle table for deduplication, generics for type safety), and appreciate how the equality guarantee—unique to Go—enables patterns impossible in Java or Python where weak references become incomparable after collection.
A possible adaptation for intermediate-level :
For an intermediate audience, the talk balances why weak pointers exist with how to use them effectively, without diving into runtime implementation details. I'd start with the 15-year history—why Go resisted, what changed, to give context for the design constraints. The core content would cover the two-method API (Make and Value), the mental model of the indirection layer (without implementation details), and the equality guarantee as a feature to rely on rather than a mechanism to understand. Practical patterns get significant time: building a WeakCache that shrinks under memory pressure, implementing canonical maps for deduplication, and the observer pattern without preventing cleanup. The talk would address common mistakes: checking Value() twice (the object could die between checks), forgetting that weak pointers don't prevent collection (obvious but often misunderstood), and when not to use weak pointers (short-lived objects, low memory pressure). Attendees would leave able to implement weak-pointer-based caches and understand when weak pointers solve their problem vs. when a regular cache with eviction policy is simpler.
Please note that Sessionize is not responsible for the accuracy or validity of the data provided by speakers. If you suspect this profile to be fake or spam, please let us know.
Jump to top