Weak Pointers Across Go, Rust, Java, and C#: Design, Implementation, and Use Cases
This article examines the concept of weak pointers and weak references in Go, Rust, Java, and C#, comparing their implementations, explaining their memory‑management semantics, presenting concrete code examples, and discussing the upcoming Go weak‑pointer proposal and practical scenarios such as caching and event handling.
The Go team introduced a unique package and simultaneously drafted two proposals: a weak package for weak pointers and a runtime change adding AddCleanup while deprecating SetFinalizer. The article first explains the weak pointer concept: a reference that does not prevent the garbage collector from reclaiming the referenced object, turning into nil when the object is collected, and can be upgraded to a strong pointer for safe access.
Other Languages' Weak Pointers
Rust
Rust provides std::rc::Weak paired with Rc for single‑threaded scenarios and std::sync::Weak paired with Arc for multi‑threaded contexts. Weak does not increase the reference count, avoiding cyclic references. It can be upgraded via upgrade(), returning Some(Rc) if the object still exists or None otherwise.
use std::rc::{Rc, Weak};
struct Node {
value: i32,
next: Option<Rc<Node>>,
prev: Option<Weak<Node>>, // weak reference to prevent cycles
}
fn main() {
let node1 = Rc::new(Node { value: 1, next: None, prev: None });
let node2 = Rc::new(Node { value: 2, next: Some(Rc::clone(&node1)), prev: Some(Rc::downgrade(&node1)) });
// Upgrade weak reference to access the previous node
if let Some(prev_node) = node2.prev.as_ref().unwrap().upgrade() {
println!("Previous node value: {}", prev_node.value);
} else {
println!("Previous node has been dropped");
}
}Java
Java uses java.lang.ref.WeakReference to hold a reference that the garbage collector may reclaim when no strong references exist. Weak references are useful for caches and listeners because the GC ignores them, preventing memory leaks. Access is via get(), which returns the strong reference if the object is still alive or null after collection.
import java.lang.ref.WeakReference;
public class WeakRefExample {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // drop strong reference
if (weakRef.get() != null) {
System.out.println("Object is still alive");
} else {
System.out.println("Object has been garbage collected");
}
}
}C#
.NET provides WeakReference (and the generic WeakReference<T>) which allows an object to be referenced without preventing its collection. The Target property (or TryGetTarget) returns the strong reference when the object is alive, otherwise null. The IsAlive property can be used for a quick liveness check.
using System;
class Program {
static void Main() {
var obj = new object();
WeakReference weakRef = new WeakReference(obj);
obj = null; // drop strong reference
if (weakRef.IsAlive) {
Console.WriteLine("Object is still alive");
} else {
Console.WriteLine("Object has been garbage collected");
}
}
}Go Weak‑Pointer Proposal
Michael Knyszek submitted a proposal that has been accepted and is expected to land in Go 1.24. The proposal defines a generic Pointer[T any] type with a Make constructor and a Value method that returns nil once the underlying object is reclaimed. Equality semantics are based on the original pointer, enabling weak‑pointer keys in maps.
// Pointer is a weak pointer to a value of type T.
// Equality is based on the original pointer, even after the object is collected.
type Pointer[T any] struct { ... }
// Make creates a weak pointer from a *T.
func Make[T any](ptr *T) Pointer[T] { ... }
// Value returns the original *T or nil if it has been collected.
func (p Pointer[T]) Value() *T { ... }The implementation stores an 8‑byte allocation per weak pointer, allowing the garbage collector to atomically set the internal pointer to nil. This design incurs minimal overhead and preserves natural equality semantics, making it suitable for building maps keyed by weak pointers.
Common Use Cases for Weak References
Cache mechanisms : Use weak references to let the GC reclaim cached data when memory is scarce.
Event handlers and callbacks : Prevent memory leaks caused by strong references from listeners to their subjects.
Large object graphs : Break cycles in complex reference structures without manual cleanup.
Example: a cache that stores values behind weak pointers, automatically cleaning up entries when the underlying key is collected.
type Cache[K any, V any] struct {
f func(*K) V
m atomic.Map[weak.Pointer[K], func() V]
}
func NewCache[K comparable, V any](f func(*K) V) *Cache[K, V] {
return &Cache[K, V]{f: f}
}
func (c *Cache[K, V]) Get(k *K) V {
kw := weak.Make(k)
if vf, ok := c.m.Load(kw); ok {
return vf()
}
vf := sync.OnceValue(func() V { return c.f(k) })
vf, loaded := c.m.LoadOrStore(kw, vf)
if !loaded {
runtime.AddCleanup(k, c.cleanup, kw)
}
return vf()
}
func (c *Cache[K, V]) cleanup(kw weak.Pointer[K]) {
c.m.Delete(kw)
}
var cached = NewCache(expensiveComputation)References:
unique package: https://pkg.go.dev/unique
weak proposal discussion: https://github.com/golang/go/issues/67552
runtime AddCleanup proposal: https://github.com/golang/go/issues/67535
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
