Summary
There is currently no way for an embedder to hold a weak reference to a JsObject.
The GC types WeakGc and Ephemeron are public, but they require &Gc<T> to construct,
and the only path from JsObject to its inner Gc<VTableObject<T>> — the inner() method — is pub(crate).
This leaves embedders without any mechanism to observe object liveness without keeping strong references that prevent collection.
The problem
Consider an embedder that maintains a Rust-side cache keyed by JS objects.
The cache should not keep JS objects alive on its own. When a JS object becomes unreachable and is collected by the GC, the corresponding cache entry should become invalid.
This requires a weak reference to JsObject: the ability to check whether the object is still alive, and to upgrade back to a strong JsObject when it is.
What is available
WeakGc<T> and Ephemeron<K, V> are pub in boa_gc.
WeakGc::upgrade() returns Option<Gc<T>> — exactly the right semantics.
WeakGc::new() requires &Gc<T>.
What is blocked
JsObject::inner() is pub(crate):
// core/engine/src/object/jsobject.rs:1041
pub(crate) fn inner(&self) -> &Gc<VTableObject<T>> {
&self.inner
}
JsObject::from_inner() is also pub(crate):
// core/engine/src/object/jsobject.rs:1045
pub(crate) fn from_inner(inner: Gc<VTableObject<T>>) -> Self {
Self { inner }
}
VTableObject itself is pub(crate).
So an external crate cannot write the type Gc<VTableObject<...>>, cannot call inner() to get it, and cannot call from_inner() to convert back. The entire WeakGc / Ephemeron API is unreachable for JsObject.
Where weak references are used today
FinalizationRegistry and WeakRef both use weak references inside boa_engine, where pub(crate) is accessible:
// finalization_registry/mod.rs
WeakGc::new(obj.inner()) // accessible within boa_engine
Ephemeron::new(target_obj.inner(), ...) // accessible within boa_engine
This means the existing weak reference infrastructure is exercised internally, but is not currently reachable from external crates.
Why this matters
Without a weak reference API, embedders who need Rust-side structures keyed by JS objects have two options, neither of which is sound:
-
Hold strong JsObject references. This prevents GC collection, leading to memory leaks.
-
Store raw pointers via transmute or similar. This breaks GC safety: the pointer may dangle after collection, ref_count becomes inconsistent, and casting back to JsObject can trigger use-after-free or ref_count underflow.
Questions
- Is the absence of a public weak reference API for
JsObject an intentional design decision, or something that hasn't been needed yet?
- If it is intentional, what approach would you recommend for embedders who need to observe object liveness without preventing GC collection?
- Is there any thoughts on exposing this kind of functionality in the future, or should embedders plan to maintain a fork for now?
Summary
There is currently no way for an embedder to hold a weak reference to a
JsObject.The GC types
WeakGcandEphemeronare public, but they require&Gc<T>to construct,and the only path from
JsObjectto its innerGc<VTableObject<T>>— theinner()method — ispub(crate).This leaves embedders without any mechanism to observe object liveness without keeping strong references that prevent collection.
The problem
Consider an embedder that maintains a Rust-side cache keyed by JS objects.
The cache should not keep JS objects alive on its own. When a JS object becomes unreachable and is collected by the GC, the corresponding cache entry should become invalid.
This requires a weak reference to
JsObject: the ability to check whether the object is still alive, and to upgrade back to a strongJsObjectwhen it is.What is available
WeakGc<T>andEphemeron<K, V>arepubinboa_gc.WeakGc::upgrade()returnsOption<Gc<T>>— exactly the right semantics.WeakGc::new()requires&Gc<T>.What is blocked
JsObject::inner()ispub(crate):JsObject::from_inner()is alsopub(crate):VTableObjectitself ispub(crate).So an external crate cannot write the type
Gc<VTableObject<...>>, cannot callinner()to get it, and cannot callfrom_inner()to convert back. The entireWeakGc/EphemeronAPI is unreachable forJsObject.Where weak references are used today
FinalizationRegistryandWeakRefboth use weak references insideboa_engine, wherepub(crate)is accessible:This means the existing weak reference infrastructure is exercised internally, but is not currently reachable from external crates.
Why this matters
Without a weak reference API, embedders who need Rust-side structures keyed by JS objects have two options, neither of which is sound:
Hold strong
JsObjectreferences. This prevents GC collection, leading to memory leaks.Store raw pointers via
transmuteor similar. This breaks GC safety: the pointer may dangle after collection,ref_countbecomes inconsistent, and casting back toJsObjectcan trigger use-after-free orref_countunderflow.Questions
JsObjectan intentional design decision, or something that hasn't been needed yet?