GO1.24: New Std-Lib weak
Go 1.24 introduces the `weak` package for managing memory more flexibly with weak pointers, preventing GC issues. Ideal for caching! 💻✨
Go 1.24 introduces a new std-lib package called weak, which allows you to create safe references to *T without preventing the garbage collector (GC) from reclaiming the memory associated with *T.
The
weakpackage provides ways to safely reference memory weakly without preventing the garbage collector's reclamation.
Much like OS.ROOT, weak is a feature that has existed in other programming languages for some time, including:
- In
Java,WeakReferenceandSoftReferenceare classic implementations primarily used for caching and object pools. These references allow automatic garbage collection when the JVM detects a memory shortage. - In
Python, theweakrefmodule allows the creation of weak references, commonly used to prevent circular reference issues or for caching. - In
C++,std::weak_ptrwas introduced alongsidestd::shared_ptrto solve circular dependency problems with shared pointers. - In
Rust,Weakis the weak reference version ofRcandArc, which helps prevent circular references and provides more flexible memory management.
Simple Definition of weak
The weak package is defined, with just a Make method and a Value method.

By using weak.Make, a weak.Pointer is created, and if the original *T has not been GC, we can access its address through weak.Pointer.Value. If the object has been collected, the Value will return nil.
You can see an example implementation of weak here: Example Code on Gist
Example Output:
➜ weak git:(main) ✗ gotip version
go version devel go1.24-18b5435 Sun Dec 15 21:41:28 2024 -0800 darwin/arm64
➜ weak git:(main) ✗ gotip run main.go
originalObject:addr 14000010050
weakPtr addr:{1400000e0d0},size:8
First GC :value: Hello, World!
originalObject clean at: 1734340907
Second GC: Weak reference value is nil
In this example:
- We create a
stringvariableoriginalObjectand useweak.Maketo create aweak.PointercalledweakPtr. - During the first garbage collection (GC), since
originalObjectis still in use,weakPtr.Valuereturns the address oforiginalObject. - In the second GC, since
originalObjectis no longer used, it is collected by the GC, andweakPtr.Valuereturnsnil.
Also, runtime.AddCleanup, a new feature in Go 1.24, works similarly to runtime.SetFinalizer by allowing you to execute code when an object is garbage collected. This feature will be covered in more detail in a later post.
Key Takeaways:
weak.Makecreates aweak.Pointer, which hides the real memory address but does not affect the GC.- If the real object is garbage collected,
weak.Pointer.Valuewill returnnil. Since we don't know when the real object will be reclaimed, always check the return value ofweak.Pointer.Value.
The Practical Use of weak
Canonicalization Maps
You might remember the unique feature introduced in Go 1.23, which uses a single pointer (8 bytes) to represent multiple identical strings, saving memory. The weak package can achieve similar functionality (In fact, in Go 1.24, unique has been refactored using weak).
Implementing a Fixed-Size Cache
type WeakCache struct {
cache map[string]weak.Pointer[list.Element] // Use weak references to store values
mu sync.Mutex
storage Storage
}
// Storage is a fixed-length cache based on doubly linked tables
type Storage struct {
capacity int // Maximum size of the cache
list *list.List
}
// Set
func (c *WeakCache) Set(key string, value any) {
// If the element already exists, update the value and move it to the head of the chain table
if elem, exists := c.cache[key]; exists {
if elemValue := elem.Value(); elemValue != nil {
elemValue.Value = &CacheItem{key: key, value: value}
c.storage.list.MoveToFront(elemValue)
elemWeak := weak.Make(elemValue)
c.cache[key] = elemWeak
return
} else {
c.removeElement(key)
}
}
// Remove the oldest unused element if capacity is full
if c.storage.list.Len() >= c.storage.capacity {
c.evict()
}
// Add new element
elem := c.storage.list.PushFront(&CacheItem{key: key, value: value})
elemWeak := weak.Make(elem)
c.cache[key] = elemWeak
}
In this example, we use a fixed-size list and a Map to store the position of each key in the list, where the value is a weak.Pointer to a list.Element. When we add a new cache item, we first check if the list is full. If so, the oldest item is evicted. When a key exists in the cache, Map[key].Value will return the address of the list element. If the item is evicted, Map[key].Value returns nil.
This design helps to create an efficient, fixed-size cache system. Using weak with a lock-free queue structure allows for even more efficient data handling.
I find weak to be quite helpful in specific scenarios. It's easy to use, and I plan to incorporate it into my projects.