ptr: add OwnedRef<T>, RefPtr::to_owned, and AnyRefCounted::ref_guard
What does this PR do?
RefPtr<T> deliberately has no Drop impl — dropping one leaks the strong ref, so every consumer balances ref_()/deref() by hand. This PR adds the owning RAII form so those call sites can be migrated type-by-type in follow-ups. No callers are migrated here, and RefPtr itself is unchanged (adding Drop to it would double-free every existing manually-balanced site).
New API in src/ptr/ref_count.rs:
OwnedRef<T>— holds exactly one strong ref and releases it onDrop(every exit path: early return,?, unwind).Derefto&T,Clonebumps the count,into_raw/from_rawfor FFI hand-offs,new/acquireconstructors. Double-release is impossible by construction: the only way to give up the ref without releasing it isinto_raw, which consumes the handle.RefPtr::to_owned(&self) -> OwnedRef<T>— safe bridge from a manually-balancedRefPtrto the RAII handle (takes an additional ref; theRefPtr's own ref is still owed).AnyRefCounted::ref_guard(&self) -> ScopedRef<Self>— RAII replacement for the hand-pairedthis.ref_(); … defer this.deref();keep-alive bracket. This one is anunsafe fn:ScopedRefcarries no lifetime, so the compiler cannot stop the guard from outliving a stack-backed*self, and the storage-governance precondition ("freed only by the count-zero destructor") goes under# Safety. It still collapses the N per-exit-pathderef()calls into one audited acquisition.- The
RefPtrdoc comment now points new code atOwnedRef.
Unsafe count (src/ptr/ref_count.rs)
Before: 89 → after: 129. The increase breaks down as 23 in the new #[cfg(test)] module (test-type trait impls that mirror the derive output, plus the raw-pointer round-trip and ref_guard tests) and the rest inside the new abstractions' bodies, each with a SAFETY: comment. This project adds the safe surface; the net reduction lands when the ~700 hand-balanced call sites migrate onto it in follow-up PRs.
Cost
Zero-cost: OwnedRef is the same NonNull<T> (+ debug-only tracking id) as RefPtr; Drop/Clone compile to the same deref()/ref_() calls the manual sites already make.
Sites that do not fully meet the "no safe-call UB" bar
ref_guardis keptunsafe fnrather than safe: the requirement that*self's storage is governed by its embedded refcount is a precondition the compiler cannot see (a stack-allocatedT: RefCountedis constructible in safe code, andScopedRefcarries no lifetime to stop the guard outliving it), so per the project rules it stays unsafe with the precondition under# Safety.ref_guard's pointer is derived from&self; if the guard holds the final ref, the destructor reclaims the allocation through that shared-provenance pointer — the same posture as the existingas_ctx_ptr()→deref()release sites, which Tree Borrows rejects. Documented under# Safety; the test for that path runs under the normal harness but is#[cfg_attr(miri, ignore)]d. The bracket use case (an outer owner survives the scope) is fully Miri-clean.
OwnedRef and RefPtr::to_owned meet the bar: every safe path acquires and releases exactly one ref, and the only safe constructor (new) heap-allocates.
Other notes
- The
scoped_log!diagnostics inref_()/deref()are compiled out ofbun_ptr's own unit-test target only (#[cfg(not(test))]): they reach theOutputSinklink interface whoseSysarm lives inbun_sys, which the leaf-crate test binary cannot link. Downstream builds are unaffected. - The ref-count debug tracker skips its
libc::backtracecapture under Miri (unsupported foreign function); it records an empty trace instead. OwnedRefis intentionally not added tolib.rs's flat re-export list — this PR confines itself toref_count.rsso the follow-up migration PRs don't conflict with it. It is reachable asbun_ptr::ref_count::OwnedRef.
How did you verify your code works?
cargo test -p bun_ptr— 12/12 pass.bun run rust:miri -p bun_ptr(-Zmiri-tree-borrows) — 9 pass, 3 ignored with documented reasons (two needThreadLock'slibc::backtrace, one exercises the&self-derived deallocation path described above). The Miri-covered set includes the fullOwnedReflifecycle for the thread-safe flavor: construct/drop balance, clone balance,into_raw/from_rawround-trip,to_owned,ref_guard, and destructor-runs-exactly-once assertions.cargo clippy -p bun_ptr --all-targets— clean.bun bd— full debug build compiles.