go/src/weak/pointer.go
Michael Anthony Knyszek 5ec76ae5aa weak: clarify Pointer equality semantics
The docs currently are imprecise about comparisons. This could lead
users to believe that objects of the same type, allocated at the same
address, could produce weak pointers that are equal to
previously-created weak pointers. This is not the case. Weak pointers
map to objects, not addresses.

Update the documentation to state precisely that if two pointers do not
compare equal, then two weak pointers created from those two pointers
are guaranteed not to compare equal. Since a future pointer pointing to
the same address is not comparable with a pointer produced *before* an
object at that address has been reclaimed, this is sufficient to explain
that weak pointers map 1:1 with object offsets, not addresses.

(An object slot cannot be reused unless that slot is unreachable, so
by construction, there's never an opportunity to compare an "old" and
"new" pointer unless one uses unsafe tricks that violate the
unsafe.Pointer rules.)

Fixes #71381.

Change-Id: I5509fd433cde013926d725694d480c697a8bc911
Reviewed-on: https://go-review.googlesource.com/c/go/+/643935
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2025-03-28 13:50:18 -07:00

98 lines
4 KiB
Go

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package weak
import (
"internal/abi"
"runtime"
"unsafe"
)
// Pointer is a weak pointer to a value of type T.
//
// Just like regular pointers, Pointer may reference any part of an
// object, such as a field of a struct or an element of an array.
// Objects that are only pointed to by weak pointers are not considered
// reachable, and once the object becomes unreachable, [Pointer.Value]
// may return nil.
//
// The primary use-cases for weak pointers are for implementing caches,
// canonicalization maps (like the unique package), and for tying together
// the lifetimes of separate values (for example, through a map with weak
// keys).
//
// Two Pointer values compare equal if and only if the pointers from which they
// were created compare equal.
// This property is maintained even after the object referenced by the pointer
// used to create a weak reference is reclaimed.
// If multiple weak pointers are made to different offsets within the same object
// (for example, pointers to different fields of the same struct), those pointers
// will not compare equal.
// In other words, weak pointers map to objects and offsets within those
// objects, not plain addresses.
// If a weak pointer is created from an object that becomes unreachable, but is
// then resurrected due to a finalizer, that weak pointer will not compare equal
// with weak pointers created after the resurrection.
//
// Calling [Make] with a nil pointer returns a weak pointer whose [Pointer.Value]
// always returns nil. The zero value of a Pointer behaves as if it were created
// by passing nil to [Make] and compares equal with such pointers.
//
// [Pointer.Value] is not guaranteed to eventually return nil.
// [Pointer.Value] may return nil as soon as the object becomes
// unreachable.
// Values stored in global variables, or that can be found by tracing
// pointers from a global variable, are reachable. A function argument or
// receiver may become unreachable at the last point where the function
// mentions it. To ensure [Pointer.Value] does not return nil,
// pass a pointer to the object to the [runtime.KeepAlive] function after
// the last point where the object must remain reachable.
//
// Note that because [Pointer.Value] is not guaranteed to eventually return
// nil, even after an object is no longer referenced, the runtime is allowed to
// perform a space-saving optimization that batches objects together in a single
// allocation slot. The weak pointer for an unreferenced object in such an
// allocation may never become nil if it always exists in the same batch as a
// referenced object. Typically, this batching only happens for tiny
// (on the order of 16 bytes or less) and pointer-free objects.
type Pointer[T any] struct {
// Mention T in the type definition to prevent conversions
// between Pointer types, like we do for sync/atomic.Pointer.
_ [0]*T
u unsafe.Pointer
}
// Make creates a weak pointer from a pointer to some value of type T.
func Make[T any](ptr *T) Pointer[T] {
// Explicitly force ptr to escape to the heap.
ptr = abi.Escape(ptr)
var u unsafe.Pointer
if ptr != nil {
u = runtime_registerWeakPointer(unsafe.Pointer(ptr))
}
runtime.KeepAlive(ptr)
return Pointer[T]{u: u}
}
// Value returns the original pointer used to create the weak pointer.
// It returns nil if the value pointed to by the original pointer was reclaimed by
// the garbage collector.
// If a weak pointer points to an object with a finalizer, then Value will
// return nil as soon as the object's finalizer is queued for execution.
func (p Pointer[T]) Value() *T {
if p.u == nil {
return nil
}
return (*T)(runtime_makeStrongFromWeak(p.u))
}
// Implemented in runtime.
//go:linkname runtime_registerWeakPointer
func runtime_registerWeakPointer(unsafe.Pointer) unsafe.Pointer
//go:linkname runtime_makeStrongFromWeak
func runtime_makeStrongFromWeak(unsafe.Pointer) unsafe.Pointer