miniextendr_api/externalptr.rs
1#![allow(rustdoc::private_intra_doc_links)]
2//! `ExternalPtr<T>` — a Box-like owned pointer that wraps R's EXTPTRSXP.
3//!
4//! This provides ownership semantics similar to `Box<T>`, with the key difference
5//! that cleanup is deferred to R's garbage collector via finalizers.
6//!
7//! # Submodules
8//!
9//! | Module | Contents |
10//! |--------|----------|
11//! | [`altrep_helpers`] | ALTREP data1/data2 slot access helpers + `Sidecar` marker type |
12//!
13//! # Core Types
14//!
15//! - [`ExternalPtr<T>`] — owned pointer wrapping EXTPTRSXP
16//! - [`TypedExternal`] — display and diagnostic metadata for stored types
17//! - [`ExternalSlice<T>`] — helper for slice data in external pointers
18//! - [`ErasedExternalPtr`] — type-erased `ExternalPtr<()>` alias
19//! - [`IntoExternalPtr`] — conversion trait for wrapping values
20//!
21//! `PartialEq`/`PartialOrd` compare the pointee values (like `Box<T>`). Use
22//! `ptr_eq` when you care about pointer identity, and `as_ref()`/`as_mut()` for
23//! explicit by-value comparisons.
24//!
25//! # Protection Strategies in miniextendr
26//!
27//! miniextendr provides three complementary protection mechanisms for different scenarios:
28//!
29//! | Strategy | Module | Lifetime | Release Order | Use Case |
30//! |----------|--------|----------|---------------|----------|
31//! | **PROTECT stack** | [`gc_protect`](crate::gc_protect) | Within `.Call` | LIFO (stack) | Temporary allocations |
32//! | **VECSXP pool** | [`protect_pool`](crate::protect_pool) | Across `.Call`s | Any order | Long-lived R objects |
33//! | **R ownership** | [`ExternalPtr`](struct@crate::externalptr::ExternalPtr) | Until R GCs | R decides | Rust data owned by R |
34//!
35//! ## When to Use ExternalPtr
36//!
37//! **Use `ExternalPtr` (this module) when:**
38//! - You want R to own a Rust value
39//! - The Rust value should be dropped when R garbage collects the pointer
40//! - You're exposing Rust structs to R code
41//!
42//! **Use [`gc_protect`](crate::gc_protect) instead when:**
43//! - You're allocating temporary R objects during computation
44//! - Protection is short-lived (within a single `.Call`)
45//!
46//! **Use [`ProtectPool`] instead when:**
47//! - You need R objects (not Rust values) to survive across `.Call`s
48//! - You need arbitrary-order release of protections
49//!
50//! ## How ExternalPtr Protection Works
51//!
52//! ```text
53//! ┌─────────────────────────────────────────────────────────────────┐
54//! │ ExternalPtr<MyStruct>::new(value) │
55//! │ ├── Rf_protect() during construction (temporary) │
56//! │ ├── R_MakeExternalPtr() creates EXTPTRSXP │
57//! │ ├── R_RegisterCFinalizerEx() registers cleanup callback │
58//! │ ├── pool.insert() roots it for the Rust handle's lifetime │
59//! │ └── Rf_unprotect() after construction complete │
60//! │ │
61//! │ Held in Rust (even across other R allocations, e.g. in a Vec) │
62//! │ └── stays alive — the pool's GC-traced VECSXP slot roots it │
63//! │ │
64//! │ Return to R → R now also references the EXTPTRSXP │
65//! │ └── Rust handle drops → pool.release(key) drops the root, │
66//! │ but R's own reference keeps it live │
67//! │ │
68//! │ R GC runs (no refs left) → finalizer (release_any) frees value │
69//! └─────────────────────────────────────────────────────────────────┘
70//! ```
71//!
72//! Owning handles (`new` / `from_raw` / `Clone`) root their `EXTPTRSXP` in a
73//! process-wide [`ProtectPool`](crate::protect_pool) so they survive R
74//! allocations while held in Rust; *borrowed* views (`wrap_sexp` / `from_sexp`
75//! / `reborrow`) take no root — the object is kept alive by whatever R-side
76//! reference handed it to them. The pool (O(1) any-order release) is used
77//! rather than `R_PreserveObject` because a `Vec<ExternalPtr>` releases its
78//! roots front-to-back, the O(n²) worst case for `R_ReleaseObject`'s
79//! precious-list scan — see `analysis/gc-protection-benchmarks-results.md`.
80//!
81//! When the end goal is an R `list()` of external pointers (rather than a
82//! `Vec<ExternalPtr>` you keep working with in Rust), prefer
83//! [`ExternalPtr::collect_into_r_list`](struct@ExternalPtr) — it builds each
84//! `EXTPTRSXP` straight into the protected result list, so the list roots every
85//! element and the pool is never touched at all.
86//!
87//! # Type Identification
88//!
89//! Type safety is enforced via `Any::downcast` (Rust's `TypeId`). R symbols
90//! in the `tag` and `prot` slots are retained for display and error messages
91//! but are **never authoritative** for downcast safety — the `Any` vtable is.
92//!
93//! Internally, data is stored as `Box<Box<dyn Any>>` — a thin pointer (fits
94//! in R's `R_ExternalPtrAddr`) pointing to a fat pointer (carries the `Any`
95//! vtable for runtime downcasting). The outer `Box` keeps the heap address
96//! stable so [`ExternalPtr::cached_ptr`](struct@ExternalPtr) can be cached
97//! once at construction.
98//!
99//! The `tag` slot holds a symbol (type name, for display).
100//! The `prot` slot holds a VECSXP (list) with two elements:
101//! - Index 0: SYMSXP (interned type ID symbol, for error messages)
102//! - Index 1: User-protected SEXP slot (for preventing GC of R objects)
103//!
104//! ## `TYPE_NAME_CSTR` vs `TYPE_ID_CSTR`
105//!
106//! [`TypedExternal`] exposes two associated constants with distinct roles —
107//! mixing them up does not break type safety (`Any::downcast` is the real
108//! gate) but produces noisy diagnostics.
109//!
110//! | Constant | Role | Visible to R as | Authoritative? |
111//! |---|---|---|---|
112//! | `TYPE_NAME_CSTR` | Display tag | `class()` / `print()` | No |
113//! | `TYPE_ID_CSTR` | Error-message identifier on downcast failure | Stored in `prot[0]` | No (cosmetic; downcast uses `TypeId`) |
114//!
115//! `#[derive(ExternalPtr)]` fills both with sensible defaults; only override
116//! manually when implementing `TypedExternal` by hand.
117//!
118//! # Pointer provenance for `cached_ptr`
119//!
120//! `ExternalPtr` caches the data pointer at construction so `as_ref` /
121//! `as_mut` avoid an FFI call on every access. The cached `*mut T` **must**
122//! be derived from a mutable path so writes through `as_mut` are sound under
123//! Stacked Borrows:
124//!
125//! - `Box::into_raw(Box::new(value))` — preferred (the constructor path).
126//! - `&mut T` — when you already hold an exclusive reference.
127//! - `<Box<dyn Any>>::downcast_mut::<T>()` — when extracting from the inner box.
128//! - [`std::ptr::from_mut`] — when promoting a `&mut T` to a raw pointer.
129//!
130//! Caching a pointer derived from `&T` or `downcast_ref::<T>()` is **UB**
131//! the moment anything writes through it. Internal sites that touch
132//! `cached_ptr` are audited; the rule matters for the (rare) hand-rolled
133//! `TypedExternal` impl that bypasses [`ExternalPtr::new`].
134//!
135//! # See also
136//!
137//! - [`crate::altrep`] — when the alternative (an ALTREP class) makes more
138//! sense than `ExternalPtr`.
139//!
140//! # ExternalPtr is Not an R Native Type
141//!
142//! Unlike R's native atomic types (`integer`, `double`, `character`, etc.),
143//! external pointers cannot be coerced to vectors or used in R's vectorized
144//! operations. This is an R limitation, not a miniextendr limitation:
145//!
146//! ```r
147//! > matrix(new("externalptr"), 1, 1)
148//! Error in `as.vector()`:
149//! ! cannot coerce type 'externalptr' to vector of type 'any'
150//! ```
151//!
152//! If you need your Rust type to participate in R's vector/matrix operations,
153//! consider implementing [`IntoList`](crate::list::IntoList) (via `#[derive(IntoList)]`)
154//! to convert your struct to a named R list, or use ALTREP to expose Rust
155//! iterators as lazy R vectors.
156
157use std::any::Any;
158use std::any::TypeId;
159use std::cell::RefCell;
160use std::fmt;
161use std::hash::{Hash, Hasher};
162use std::marker::PhantomData;
163use std::mem::{self, ManuallyDrop, MaybeUninit};
164use std::ops::{Deref, DerefMut};
165use std::pin::Pin;
166use std::ptr::{self, NonNull};
167
168use crate::protect_pool::{ProtectKey, ProtectPool};
169use crate::sys::{
170 R_ClearExternalPtr, R_ExternalPtrAddr, R_ExternalPtrProtected, R_ExternalPtrTag,
171 R_MakeExternalPtr, R_MakeExternalPtr_unchecked, R_RegisterCFinalizerEx,
172 R_RegisterCFinalizerEx_unchecked, R_UnboundValue, R_getVarEx, Rf_allocVector,
173 Rf_allocVector_unchecked, Rf_install, Rf_install_unchecked, Rf_protect, Rf_protect_unchecked,
174 Rf_unprotect, Rf_unprotect_unchecked,
175};
176use crate::{R_xlen_t, Rboolean, SEXP, SEXPTYPE, SexpExt};
177
178/// A wrapper around a raw pointer that implements [`Send`].
179///
180/// # Safety
181///
182/// This is safe to send between threads because it's just a memory address.
183/// The data is owned and transferred to the main thread before being accessed.
184type SendableAnyPtr = crate::worker::Sendable<NonNull<Box<dyn Any>>>;
185
186/// Create a new sendable pointer from a raw `*mut Box<dyn Any>`.
187///
188/// # Safety
189///
190/// The pointer must be non-null.
191#[inline]
192unsafe fn sendable_any_ptr_new(ptr: *mut Box<dyn Any>) -> SendableAnyPtr {
193 // SAFETY: Caller guarantees ptr is non-null
194 crate::worker::Sendable(unsafe { NonNull::new_unchecked(ptr) })
195}
196
197/// Get the raw pointer, consuming the sendable wrapper.
198#[inline]
199fn sendable_any_ptr_into_ptr(ptr: SendableAnyPtr) -> *mut Box<dyn Any> {
200 ptr.0.as_ptr()
201}
202
203/// Index of the type SYMSXP contained in the `prot` (a `VECSXP` list)
204const PROT_TYPE_ID_INDEX: isize = 0;
205/// Index of user-protected objects contained in the `prot` (a `VECSXP` list)
206const PROT_USER_INDEX: isize = 1;
207/// Length of the `prot` list (`VECSXP`)
208const PROT_VEC_LEN: isize = 2;
209
210#[inline]
211fn is_type_erased<T: 'static>() -> bool {
212 TypeId::of::<T>() == TypeId::of::<()>()
213}
214
215/// Get the interned R symbol for a type's name.
216///
217/// R interns symbols via `Rf_install`, so the same string always returns the
218/// same pointer. The symbol is stable display metadata; `Any::downcast` is the
219/// authoritative type check.
220///
221/// # Safety
222///
223/// Must be called from R's main thread.
224#[inline]
225unsafe fn type_symbol<T: TypedExternal>() -> SEXP {
226 unsafe { Rf_install(T::TYPE_NAME_CSTR.as_ptr().cast()) }
227}
228
229/// Unchecked version of [`type_symbol`] - no thread safety checks.
230///
231/// # Safety
232///
233/// Must be called from R's main thread. No debug assertions.
234#[inline]
235unsafe fn type_symbol_unchecked<T: TypedExternal>() -> SEXP {
236 unsafe { Rf_install_unchecked(T::TYPE_NAME_CSTR.as_ptr().cast()) }
237}
238
239/// Get the namespaced type ID symbol used in diagnostics.
240///
241/// Uses `TYPE_ID_CSTR`, which includes the module path to make mismatch
242/// messages unambiguous.
243///
244/// # Safety
245///
246/// Must be called from R's main thread.
247#[inline]
248unsafe fn type_id_symbol<T: TypedExternal>() -> SEXP {
249 unsafe { Rf_install(T::TYPE_ID_CSTR.as_ptr().cast()) }
250}
251
252/// Unchecked version of [`type_id_symbol`].
253///
254/// # Safety
255///
256/// Must be called from R's main thread. No debug assertions.
257#[inline]
258unsafe fn type_id_symbol_unchecked<T: TypedExternal>() -> SEXP {
259 unsafe { Rf_install_unchecked(T::TYPE_ID_CSTR.as_ptr().cast()) }
260}
261
262/// Get the type name from a stored symbol SEXP.
263///
264/// # Safety
265///
266/// `sym` must be a valid SYMSXP.
267#[inline]
268fn symbol_name(sym: SEXP) -> &'static str {
269 use crate::SexpExt;
270 let printname = sym.printname();
271 let cstr = printname.r_char();
272 let len = printname.len();
273 unsafe {
274 std::str::from_utf8(std::slice::from_raw_parts(cstr.cast(), len))
275 .expect("R SYMSXP PRINTNAME is not valid UTF-8")
276 }
277}
278
279// region: TypedExternalPtr Trait
280
281/// Trait for types that can be stored in an ExternalPtr.
282///
283/// This provides R-visible display and diagnostic identifiers. Runtime type
284/// checking is performed by `Any::downcast` (Rust's `TypeId`), not by comparing
285/// these symbols.
286///
287/// # Type ID vs Type Name
288///
289/// - `TYPE_ID_CSTR`: Namespaced identifier used in mismatch diagnostics (stored in `prot[0]`).
290/// Format: `"<crate_name>@<crate_version>::<module_path>::<type_name>\0"`
291///
292/// The crate name, version, and module path distinguish otherwise similar
293/// names in error messages. They do not determine compatibility.
294///
295/// - `TYPE_NAME_CSTR`: Short display name for the R tag (shown when printing).
296/// Just the type identifier for readability.
297pub trait TypedExternal: 'static {
298 /// The type name as a static string (for debugging and display)
299 const TYPE_NAME: &'static str;
300
301 /// The type name as a null-terminated C string (for R tag display)
302 const TYPE_NAME_CSTR: &'static [u8];
303
304 /// Namespaced type ID as a null-terminated C string (for diagnostics).
305 ///
306 /// This should include the module path to prevent ambiguous messages.
307 /// Use `concat!(module_path!(), "::", stringify!(Type), "\0").as_bytes()`
308 /// when implementing manually, or use `#[derive(ExternalPtr)]`.
309 const TYPE_ID_CSTR: &'static [u8];
310}
311
312/// Marker trait for types that should be converted to R as ExternalPtr.
313///
314/// When a type implements this trait (via `#[derive(ExternalPtr)]`), it gets a
315/// blanket `IntoR` implementation that wraps the value in `ExternalPtr<T>`.
316///
317/// This allows returning the type directly from `#[miniextendr]` functions:
318///
319/// ```ignore
320/// #[derive(ExternalPtr)]
321/// struct MyData { value: i32 }
322///
323/// #[miniextendr]
324/// fn create_data(v: i32) -> MyData {
325/// MyData { value: v } // Automatically wrapped in ExternalPtr
326/// }
327/// ```
328pub trait IntoExternalPtr: TypedExternal {}
329
330impl TypedExternal for () {
331 const TYPE_NAME: &'static str = "()";
332 const TYPE_NAME_CSTR: &'static [u8] = b"()\0";
333 // Unit type is special - same ID as name since it's only used for type-erased ptrs
334 const TYPE_ID_CSTR: &'static [u8] = b"()\0";
335}
336// endregion
337
338// region: Class-handle unwrapping (audit A9)
339
340/// Look up a variable bound directly in a single environment frame (no search
341/// of enclosing frames — `R_getVarEx` with `inherits = FALSE`, the API-blessed
342/// replacement for the removed `Rf_findVarInFrame`).
343///
344/// Returns `None` if `env` is not itself an environment, or if `name` has no
345/// binding in it. Active bindings are forced transparently by R, same as any
346/// other variable read. Note: `R_getVarEx` longjmps (raises an R error) if
347/// the binding turns out to be `R_MissingArg` — pathological for the
348/// `.ptr`/`.__enclos_env__`/`private` handle lookups this function serves,
349/// and acceptable here since callers run under the framework's unwind
350/// protection.
351///
352/// # Safety
353///
354/// Must be called from R's main thread.
355unsafe fn env_binding(env: SEXP, name: &std::ffi::CStr) -> Option<SEXP> {
356 unsafe {
357 if !env.is_environment() {
358 return None;
359 }
360 let sym = Rf_install(name.as_ptr());
361 let val = R_getVarEx(sym, env, Rboolean::FALSE, R_UnboundValue);
362 if ptr::addr_eq(val.0, R_UnboundValue.0) {
363 None
364 } else {
365 Some(val)
366 }
367 }
368}
369
370/// Attempt to unwrap a class-wrapped handle down to the bare `EXTPTRSXP` it
371/// carries, so [`ExternalPtr::<T>`](ExternalPtr) argument conversion accepts
372/// the ergonomic class handle (e.g. `Foo$new(...)`) in addition to the raw
373/// pointer returned by a low-level constructor (audit finding A9 —
374/// `audit/2026-07-03-api-sense-conversions-dataframe-errors.md` #5).
375///
376/// Tries, in order:
377/// - **Env / R6**: a direct `.ptr` binding on `sexp` itself — most
378/// `#[miniextendr(env)]` classes are actually a bare classed `EXTPTRSXP`
379/// (the generated constructor does `class(.val) <- "T"` directly on the
380/// pointer returned by Rust, see `env_class.rs`), which already satisfies
381/// the plain `EXTPTRSXP` check and never reaches this function, but a
382/// user-authored environment that binds `.ptr` is covered here too. Then
383/// the R6 handle chain `.__enclos_env__` -> `private` -> `.ptr` (R6
384/// objects are the *public* environment; `private` only hangs off the
385/// enclosing environment stored at `.__enclos_env__` for `portable`
386/// classes, the default — see `r6_class.rs`).
387/// - **S4**: the `ptr` slot via `methods::slot()`
388/// ([`crate::s4_helpers::s4_get_slot`]). Guarded by `isS4()`, which
389/// excludes S7 objects even though both share the `S4SXP`/`OBJSXP`
390/// `SEXPTYPE` — S7's `new_object(S7_object(), ...)` base never sets the S4
391/// bit.
392/// - **Anything else carrying a `.ptr` attribute**: S7 stores properties as
393/// plain attributes on its base object (see `s7_class.rs`), so
394/// `Rf_getAttrib(x, ".ptr")` recovers the pointer without going through
395/// S7's `@`/`prop()` dispatch machinery.
396///
397/// Returns `Some(inner)` only when the unwrapped value is itself an
398/// `EXTPTRSXP` — anything else (e.g. a `.ptr`-named field that isn't a
399/// pointer) is treated as "no handle found" rather than an error. No
400/// recursion beyond one unwrap level. `Any::downcast` remains the type-safety
401/// authority: unwrapping a handle for the *wrong* `T` still fails at the
402/// caller with the existing type-mismatch error — this only loosens the
403/// accepted R-side shape, not type safety.
404///
405/// # Safety
406///
407/// - Must be called from R's main thread.
408/// - The returned SEXP is reachable from `sexp` (an env binding, S4 slot, or
409/// attribute) for as long as `sexp` itself is protected. Macro-generated
410/// `.Call()` wrappers hold every argument alive in the call's PROTECT stack
411/// for the duration of the call, so no additional protection is needed
412/// here.
413pub(crate) unsafe fn unwrap_class_handle(sexp: SEXP) -> Option<SEXP> {
414 unsafe {
415 if sexp.is_environment() {
416 if let Some(direct) = env_binding(sexp, c".ptr") {
417 if direct.type_of() == SEXPTYPE::EXTPTRSXP {
418 return Some(direct);
419 }
420 }
421 let enclos = env_binding(sexp, c".__enclos_env__")?;
422 let private = env_binding(enclos, c"private")?;
423 let inner = env_binding(private, c".ptr")?;
424 return (inner.type_of() == SEXPTYPE::EXTPTRSXP).then_some(inner);
425 }
426
427 if sexp.is_s4() {
428 let slot = crate::s4_helpers::s4_get_slot(sexp, "ptr").ok()?;
429 return (slot.type_of() == SEXPTYPE::EXTPTRSXP).then_some(slot);
430 }
431
432 let attr = sexp.get_attr(Rf_install(c".ptr".as_ptr()));
433 (attr.type_of() == SEXPTYPE::EXTPTRSXP).then_some(attr)
434 }
435}
436// endregion
437
438// region: ExternalPtr<T>
439
440/// An owned pointer stored in R's external pointer SEXP.
441///
442/// This is conceptually similar to `Box<T>`, but with the following differences:
443/// - Memory is freed by R's GC via a registered finalizer (non-deterministic)
444/// - The underlying SEXP is Copy, so aliasing must be manually prevented
445/// - Type checking happens at runtime via `Any::downcast` (Rust `TypeId`)
446///
447/// # Thread Safety
448///
449/// `ExternalPtr` is `Send` to allow returning from worker thread functions.
450/// However, **concurrent access is not allowed** - R's runtime is single-threaded.
451/// All R API calls are serialized through the main thread via `with_r_thread`.
452///
453/// # Safety
454///
455/// The ExternalPtr assumes exclusive ownership of the underlying data.
456/// Cloning the raw SEXP without proper handling will lead to double-free.
457///
458/// # Examples
459///
460/// ```no_run
461/// use miniextendr_api::externalptr::{ExternalPtr, TypedExternal};
462///
463/// struct MyData { value: f64 }
464/// impl TypedExternal for MyData {
465/// const TYPE_NAME: &'static str = "MyData";
466/// const TYPE_NAME_CSTR: &'static [u8] = b"MyData\0";
467/// const TYPE_ID_CSTR: &'static [u8] = b"my_crate::MyData\0";
468/// }
469///
470/// let ptr = ExternalPtr::new(MyData { value: 3.14 });
471/// assert_eq!(ptr.as_ref().unwrap().value, 3.14);
472/// ```
473#[repr(C)]
474pub struct ExternalPtr<T: TypedExternal> {
475 sexp: SEXP,
476 /// Cached data pointer, set once at construction time.
477 ///
478 /// This avoids the `R_ExternalPtrAddr` FFI call on every `as_ref()`/`as_mut()`.
479 /// The pointer remains valid for the lifetime of the `ExternalPtr` because:
480 /// - R's finalizer only runs after R garbage-collects the SEXP (which cannot
481 /// happen while a Rust `ExternalPtr` value exists).
482 /// - `R_ClearExternalPtr` is only called in methods that consume or finalize
483 /// (`into_raw`, `into_inner`, `release_any`).
484 cached_ptr: NonNull<T>,
485 /// The [`ProtectPool`] key rooting this handle's `EXTPTRSXP`, or `None` for
486 /// borrowed views.
487 ///
488 /// `Some(key)` for *owning* handles built from a fresh value (`new` /
489 /// `new_unchecked` / `from_raw` / `Clone` / `Default`): the constructor
490 /// roots the `EXTPTRSXP` in the main-thread pool so it stays alive for the
491 /// whole Rust lifetime of the handle — including while it sits in a `Vec`
492 /// across other R allocations before being handed to R (#836). `Drop` /
493 /// `into_raw` / `into_inner` release the root via the key.
494 ///
495 /// `None` for *borrowed* views of an SEXP R already owns (`wrap_sexp*` /
496 /// `from_sexp*` / `reborrow`): no root is taken, so none is released. The
497 /// aliased object is kept alive by whatever R-side reference handed it to us
498 /// (a `.Call` argument frame, an owning sibling handle, …).
499 root: Option<ProtectKey>,
500 _marker: PhantomData<T>,
501}
502
503// SAFETY: ExternalPtr can be sent between threads because:
504// 1. All R API operations are serialized through the main thread via with_r_thread
505// 2. The worker thread is blocked while the main thread processes R calls
506// 3. There is no concurrent access - only sequential hand-off between threads
507unsafe impl<T: TypedExternal + Send> Send for ExternalPtr<T> {}
508
509// region: ExternalPtr GC roots
510//
511// Owning `ExternalPtr` handles keep their `EXTPTRSXP` alive for the handle's
512// whole Rust lifetime by rooting it in a process-wide `ProtectPool` — a single
513// GC-traced VECSXP with Rust-side slot bookkeeping. This is what makes a naive
514// `Vec<ExternalPtr<T>>` GC-safe: every element stays rooted while later elements
515// allocate (#836).
516//
517// Why a pool and not `R_PreserveObject`: the pool releases in O(1) any order,
518// whereas `R_ReleaseObject` scans R's precious list (O(n)). A `Vec<ExternalPtr>`
519// drops front-to-back — oldest first, i.e. the entries deepest in R's LIFO
520// precious list — so `R_PreserveObject` rooting degrades to O(n²) on exactly
521// this workload (60–65× slower at 10k; see
522// analysis/gc-protection-benchmarks-results.md). The pool is the mechanism the
523// strategy analysis prescribes for ExternalPtr (analysis/gc-protection-strategies.md).
524//
525// `ProtectPool` is `!Send`/`!Sync` and lives in a `thread_local!` on R's main
526// thread. Every access happens there: roots are taken inside
527// `create_extptr_sexp[_unchecked]` (main-thread by contract / `with_r_thread`),
528// and released through `with_r_thread` from `Drop` / `into_raw` / `into_inner`.
529// The pool is wrapped in `ManuallyDrop` so it is never released at thread exit —
530// it is a session-lifetime root table, and running `R_ReleaseObject` on its
531// backing during R's own teardown would touch a half-freed R heap.
532
533thread_local! {
534 static EXTPTR_ROOTS: RefCell<Option<ManuallyDrop<ProtectPool>>> = const { RefCell::new(None) };
535}
536
537/// Root an owning handle's `EXTPTRSXP` in the main-thread pool.
538///
539/// Must run on R's main thread with `sexp` already protected by the caller (the
540/// pool may allocate while growing). Both hold inside
541/// `create_extptr_sexp[_unchecked]`.
542#[inline]
543fn root_owned(sexp: SEXP) -> ProtectKey {
544 EXTPTR_ROOTS.with_borrow_mut(|slot| {
545 let pool = slot.get_or_insert_with(|| {
546 // SAFETY: on R's main thread (caller contract); R is initialized
547 // (we are mid-`create_extptr_sexp`, allocating R objects).
548 ManuallyDrop::new(unsafe { ProtectPool::new(ProtectPool::DEFAULT_CAPACITY) })
549 });
550 // SAFETY: on R's main thread; `sexp` is live (protected by the caller).
551 unsafe { pool.insert(sexp) }
552 })
553}
554
555/// Release an owning handle's pool root. Stale keys are a safe no-op.
556///
557/// Must run on R's main thread (callers route through `with_r_thread`).
558#[inline]
559fn unroot_owned(key: ProtectKey) {
560 EXTPTR_ROOTS.with_borrow_mut(|slot| {
561 if let Some(pool) = slot.as_mut() {
562 // SAFETY: on R's main thread.
563 unsafe { pool.release(key) };
564 }
565 });
566}
567// endregion
568
569impl<T: TypedExternal> ExternalPtr<T> {
570 /// Build an *owning* handle rooted at `root`.
571 ///
572 /// Pairs with the [`ProtectPool`] root that [`create_extptr_sexp`] /
573 /// [`create_extptr_sexp_unchecked`] take on the SEXP (and return as the
574 /// key). The root is released by `Drop` / `into_raw` / `into_inner`. Only
575 /// the four fresh-value constructors (`new` / `new_unchecked` / `from_raw` /
576 /// `from_raw_unchecked`) build through here.
577 ///
578 /// [`create_extptr_sexp`]: Self::create_extptr_sexp
579 /// [`create_extptr_sexp_unchecked`]: Self::create_extptr_sexp_unchecked
580 #[inline]
581 fn from_owned_parts(sexp: SEXP, cached_ptr: NonNull<T>, root: ProtectKey) -> Self {
582 Self {
583 sexp,
584 cached_ptr,
585 root: Some(root),
586 _marker: PhantomData,
587 }
588 }
589
590 /// Build a *borrowed* view (`root = None`) of an SEXP R already owns.
591 ///
592 /// No GC root is taken and none is released — the aliased object is kept
593 /// alive by the R-side reference that handed it to us. Used by every
594 /// `wrap_sexp*` / `from_sexp*` / `reborrow` path.
595 #[inline]
596 fn from_borrowed_parts(sexp: SEXP, cached_ptr: NonNull<T>) -> Self {
597 Self {
598 sexp,
599 cached_ptr,
600 root: None,
601 _marker: PhantomData,
602 }
603 }
604
605 /// Release the pool root iff this handle owns one.
606 ///
607 /// Routed through [`with_r_thread`] because an owning `ExternalPtr` is
608 /// `Send` and may be dropped on the worker thread, while the pool lives on
609 /// R's main thread. `with_r_thread` runs the closure inline when already on
610 /// the main thread (the common case), so this is a direct pool release
611 /// there and a thread hand-off only from the worker. `ProtectKey` is `Copy`
612 /// + `Send` (two `u32`s), so it crosses the boundary by value.
613 ///
614 /// [`with_r_thread`]: crate::worker::with_r_thread
615 #[inline]
616 fn release_root_if_owned(&self) {
617 let Some(key) = self.root else {
618 return;
619 };
620 crate::worker::with_r_thread(move || unroot_owned(key));
621 }
622
623 /// Allocates memory on the heap and places `x` into it.
624 ///
625 /// Internally stores a `Box<Box<dyn Any>>` — a thin pointer (fits in R's
626 /// `R_ExternalPtrAddr`) pointing to a fat pointer (carries the `Any` vtable
627 /// for runtime type checking via `downcast`).
628 ///
629 /// This function can be called from the two supported R contexts:
630 /// - If called from R's main thread, creates the ExternalPtr directly
631 /// - If called from the worker thread (during `run_on_worker`), automatically
632 /// sends the R API calls to the main thread via [`with_r_thread`]
633 ///
634 /// # Panics
635 ///
636 /// Panics if called from a non-main thread outside of a `run_on_worker` context.
637 ///
638 /// Equivalent to `Box::new`.
639 ///
640 /// [`with_r_thread`]: crate::worker::with_r_thread
641 #[inline]
642 pub fn new(x: T) -> Self {
643 // Get concrete pointer with full write provenance from Box::into_raw,
644 // BEFORE erasing to dyn Any. This preserves mutable provenance for
645 // cached_ptr (downcast_ref would give shared-reference provenance,
646 // which is UB for later writes through as_mut()).
647 let raw: *mut T = Box::into_raw(Box::new(x));
648 // SAFETY: Box::into_raw never returns null
649 let cached_ptr = unsafe { NonNull::new_unchecked(raw) };
650
651 // Re-wrap: Box::from_raw(raw) → Box<dyn Any> → Box<Box<dyn Any>>
652 // The data stays at `raw`; we're just adding the Any vtable wrapper.
653 let inner: Box<dyn Any> = unsafe { Box::from_raw(raw) };
654 let any_raw: *mut Box<dyn Any> = Box::into_raw(Box::new(inner));
655
656 // Wrap in Sendable so it can be sent across thread boundary
657 let sendable = unsafe { sendable_any_ptr_new(any_raw) };
658
659 // Use with_r_thread to run R API calls on main thread. The pool root is
660 // taken there (on the main thread, where the pool lives) and the key
661 // crosses back by value — `(SEXP, ProtectKey)` is `Send`.
662 let (sexp, root) = crate::worker::with_r_thread(move || {
663 let any_raw = sendable_any_ptr_into_ptr(sendable);
664 unsafe { Self::create_extptr_sexp_unchecked(any_raw) }
665 });
666
667 Self::from_owned_parts(sexp, cached_ptr, root)
668 }
669
670 /// Allocates memory on the heap and places `x` into it, without thread checks.
671 ///
672 /// # Safety
673 ///
674 /// Must be called from R's main thread. Calling from another thread
675 /// is undefined behavior (R APIs are not thread-safe).
676 #[inline]
677 pub unsafe fn new_unchecked(x: T) -> Self {
678 let raw: *mut T = Box::into_raw(Box::new(x));
679 let cached_ptr = unsafe { NonNull::new_unchecked(raw) };
680
681 let inner: Box<dyn Any> = unsafe { Box::from_raw(raw) };
682 let any_raw: *mut Box<dyn Any> = Box::into_raw(Box::new(inner));
683
684 let (sexp, root) = unsafe { Self::create_extptr_sexp_unchecked(any_raw) };
685 Self::from_owned_parts(sexp, cached_ptr, root)
686 }
687
688 /// Create an EXTPTRSXP from a `*mut Box<dyn Any>`. Must be called from main thread.
689 ///
690 /// The `any_raw` is a thin pointer to a heap-allocated fat pointer (`Box<dyn Any>`).
691 /// R stores the thin pointer in `R_ExternalPtrAddr`. Returns the SEXP and the
692 /// [`ProtectPool`] key that roots it for the owning handle's lifetime.
693 #[inline]
694 unsafe fn create_extptr_sexp(any_raw: *mut Box<dyn Any>) -> (SEXP, ProtectKey) {
695 debug_assert!(
696 !any_raw.is_null(),
697 "create_extptr_sexp received null pointer"
698 );
699
700 let type_sym = unsafe { type_symbol::<T>() };
701 let type_id_sym = unsafe { type_id_symbol::<T>() };
702
703 // keep raw: this protect/unprotect straddles the ProtectPool handoff
704 // (`root_owned` below), a two-stage rooting boundary that outlives this
705 // function via the pool key — not a lexical RAII scope. `OwnedProtect` /
706 // `ProtectScope` would misrepresent the ownership transfer.
707 let prot = unsafe { Rf_allocVector(SEXPTYPE::VECSXP, PROT_VEC_LEN) };
708 unsafe { Rf_protect(prot) };
709 prot.set_vector_elt(PROT_TYPE_ID_INDEX, type_id_sym);
710
711 let sexp = unsafe { R_MakeExternalPtr(any_raw.cast(), type_sym, prot) };
712 unsafe { Rf_protect(sexp) };
713
714 // Non-generic finalizer — Box<dyn Any> vtable handles the concrete drop
715 unsafe { R_RegisterCFinalizerEx(sexp, Some(release_any), Rboolean::TRUE) };
716
717 // Root the owning handle for its whole Rust lifetime so it survives R
718 // allocations while held (e.g. element-by-element in a `Vec`) before
719 // reaching R (#836). The pool gives O(1) any-order release — see the
720 // `EXTPTR_ROOTS` docs for why that beats `R_PreserveObject` here. `sexp`
721 // is still protected, so the pool may safely allocate while growing.
722 // Must happen here, on the main thread, because `new` returns the SEXP
723 // to the *calling* thread (possibly the worker) where R API is gone.
724 let root = root_owned(sexp);
725
726 unsafe { Rf_unprotect(2) };
727 (sexp, root)
728 }
729
730 /// Create an EXTPTRSXP from a `*mut Box<dyn Any>` without thread safety checks.
731 ///
732 /// # Safety
733 ///
734 /// Must be called from R's main thread. No debug assertions for thread safety.
735 ///
736 /// Returns the SEXP and the [`ProtectPool`] key that roots it.
737 #[inline]
738 unsafe fn create_extptr_sexp_unchecked(any_raw: *mut Box<dyn Any>) -> (SEXP, ProtectKey) {
739 debug_assert!(
740 !any_raw.is_null(),
741 "create_extptr_sexp_unchecked received null pointer"
742 );
743
744 let type_sym = unsafe { type_symbol_unchecked::<T>() };
745 let type_id_sym = unsafe { type_id_symbol_unchecked::<T>() };
746
747 let prot = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, PROT_VEC_LEN) };
748 unsafe { Rf_protect_unchecked(prot) };
749 unsafe { prot.set_vector_elt_unchecked(PROT_TYPE_ID_INDEX, type_id_sym) };
750
751 let sexp = unsafe { R_MakeExternalPtr_unchecked(any_raw.cast(), type_sym, prot) };
752 unsafe { Rf_protect_unchecked(sexp) };
753
754 // Non-generic finalizer — Box<dyn Any> vtable handles the concrete drop
755 unsafe {
756 R_RegisterCFinalizerEx_unchecked(sexp, Some(release_any), Rboolean::TRUE);
757 };
758
759 // Root the owning handle (see `create_extptr_sexp` for the rationale).
760 // `root_owned` uses the pool's checked FFI, which runs inline here
761 // because the unchecked constructors are main-thread-by-contract; `sexp`
762 // is still protected, covering any allocation inside a pool grow.
763 let root = root_owned(sexp);
764
765 unsafe { Rf_unprotect_unchecked(2) };
766 (sexp, root)
767 }
768
769 /// Collect an iterator of values into a protected R list (`VECSXP`) holding
770 /// one fresh external pointer per item, rooting each via the destination
771 /// list instead of the [`ProtectPool`](crate::protect_pool).
772 ///
773 /// This is the GC-safe, allocation-lean way to hand many Rust values to R at
774 /// once — e.g. converting a `Vec<T>` into an R `list()` of external pointers.
775 /// Each `EXTPTRSXP` is created and **immediately** stored into the
776 /// already-protected result list, so the list roots it the instant it
777 /// exists: there is no unprotected window between element allocations, and
778 /// **no per-element pool traffic**.
779 ///
780 /// Contrast the naive `items.map(ExternalPtr::new).collect::<Vec<_>>()`,
781 /// which roots every handle in the process-wide pool (keeping the `Vec`
782 /// GC-safe while held — #836) only to release every root again when the `Vec`
783 /// drops, then still needs a second pass to copy the handles into a list.
784 /// Here the list *is* the root, so both the pool round-trip and the copy
785 /// pass are skipped. The whole batch also crosses to R's main thread in a
786 /// single [`with_r_thread`](crate::worker::with_r_thread) hop rather than one
787 /// per element.
788 ///
789 /// The returned `VECSXP` is **not** protected: the caller must protect it or
790 /// return it to R immediately, exactly like any other freshly built SEXP
791 /// (e.g. an [`IntoR`](crate::IntoR) result).
792 pub fn collect_into_r_list<I>(items: I) -> SEXP
793 where
794 I: IntoIterator<Item = T>,
795 {
796 // Box + type-erase every value on the *calling* thread (no R API needed),
797 // then ship only the raw thin pointers to the main thread — the same
798 // ownership transfer `new` performs, batched. `Sendable` carries the Vec
799 // across the boundary; the values are owned and handed off, never aliased.
800 let raws: Vec<*mut Box<dyn Any>> = items
801 .into_iter()
802 .map(|x| {
803 let inner: Box<dyn Any> = Box::new(x);
804 Box::into_raw(Box::new(inner))
805 })
806 .collect();
807 let sendable = crate::worker::Sendable(raws);
808
809 crate::worker::with_r_thread(move || {
810 let raws = sendable.0;
811 // SAFETY: `with_r_thread` runs this on R's main thread; every entry
812 // is a live `Box<Box<dyn Any>>` wrapping a `T`, ownership transferred.
813 unsafe { Self::build_extptr_list(&raws) }
814 })
815 }
816
817 /// Build a protected `VECSXP` of external pointers from already-erased boxes.
818 ///
819 /// Allocates the result list, protects it, then creates one `EXTPTRSXP` per
820 /// entry directly into its slot — rooted by the protected list, no pool. The
821 /// type symbols are interned once and reused (they are never GC'd, so they
822 /// stay valid across the allocating loop). Returns the list **unprotected**.
823 ///
824 /// # Safety
825 ///
826 /// Must run on R's main thread; each `raw` must be a live `Box<Box<dyn Any>>`
827 /// wrapping a `T`, with ownership transferred to the new external pointer.
828 unsafe fn build_extptr_list(raws: &[*mut Box<dyn Any>]) -> SEXP {
829 let n = R_xlen_t::try_from(raws.len()).expect("list length exceeds R_xlen_t::MAX");
830 let list = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, n) };
831 unsafe { Rf_protect_unchecked(list) };
832
833 let type_sym = unsafe { type_symbol_unchecked::<T>() };
834 let type_id_sym = unsafe { type_id_symbol_unchecked::<T>() };
835
836 for (i, &any_raw) in raws.iter().enumerate() {
837 let idx = R_xlen_t::try_from(i).expect("index exceeds R_xlen_t::MAX");
838 // SAFETY: main thread; `any_raw` owns a `T`; `list` is protected, so
839 // it roots each element the instant `set_vector_elt` stores it.
840 unsafe { Self::make_extptr_into_slot(any_raw, type_sym, type_id_sym, list, idx) };
841 }
842
843 unsafe { Rf_unprotect_unchecked(1) };
844 list
845 }
846
847 /// Create an `EXTPTRSXP` for `any_raw` and store it into `dest[idx]`.
848 ///
849 /// Mirrors [`create_extptr_sexp_unchecked`](Self::create_extptr_sexp_unchecked)
850 /// but roots the new pointer via `dest` (which the caller keeps protected)
851 /// instead of the pool — the element is live the instant it lands in the
852 /// protected list, so a bulk build pays no pool insert/release per element.
853 ///
854 /// # Safety
855 ///
856 /// Must run on R's main thread; `any_raw` must own a `T`; `dest` must be a
857 /// protected `VECSXP` with `idx` in bounds; `type_sym` / `type_id_sym` must
858 /// be the interned symbols for `T`.
859 #[inline]
860 unsafe fn make_extptr_into_slot(
861 any_raw: *mut Box<dyn Any>,
862 type_sym: SEXP,
863 type_id_sym: SEXP,
864 dest: SEXP,
865 idx: R_xlen_t,
866 ) {
867 let prot = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, PROT_VEC_LEN) };
868 unsafe { Rf_protect_unchecked(prot) };
869 unsafe { prot.set_vector_elt_unchecked(PROT_TYPE_ID_INDEX, type_id_sym) };
870
871 let sexp = unsafe { R_MakeExternalPtr_unchecked(any_raw.cast(), type_sym, prot) };
872 unsafe { Rf_protect_unchecked(sexp) };
873 unsafe { R_RegisterCFinalizerEx_unchecked(sexp, Some(release_any), Rboolean::TRUE) };
874
875 // Root via the destination list instead of the pool: `dest` is protected
876 // by the caller, so storing `sexp` keeps it (and its `prot`) alive with
877 // no pool churn.
878 unsafe { dest.set_vector_elt_unchecked(idx, sexp) };
879
880 unsafe { Rf_unprotect_unchecked(2) };
881 }
882
883 /// Constructs a new `ExternalPtr` with uninitialized contents.
884 ///
885 /// Equivalent to `Box::new_uninit`.
886 #[inline]
887 pub fn new_uninit() -> ExternalPtr<MaybeUninit<T>>
888 where
889 MaybeUninit<T>: TypedExternal,
890 {
891 ExternalPtr::new(MaybeUninit::uninit())
892 }
893
894 /// Constructs a new `ExternalPtr` with zeroed contents.
895 ///
896 /// Equivalent to `Box::new_zeroed`.
897 #[inline]
898 pub fn new_zeroed() -> ExternalPtr<MaybeUninit<T>>
899 where
900 MaybeUninit<T>: TypedExternal,
901 {
902 ExternalPtr::new(MaybeUninit::zeroed())
903 }
904
905 /// Constructs an ExternalPtr from a raw pointer.
906 ///
907 /// Re-wraps the `*mut T` in `Box<dyn Any>` for the new storage format.
908 ///
909 /// # Safety
910 ///
911 /// - `raw` must have been allocated via `Box::into_raw` or equivalent
912 /// - `raw` must not be null
913 /// - Caller transfers ownership to the ExternalPtr
914 /// - Must be called from R's main thread
915 ///
916 /// Equivalent to `Box::from_raw`.
917 #[inline]
918 pub unsafe fn from_raw(raw: *mut T) -> Self {
919 // Re-wrap in Box<dyn Any> → Box<Box<dyn Any>>
920 let inner: Box<dyn Any> = unsafe { Box::from_raw(raw) };
921 let outer: Box<Box<dyn Any>> = Box::new(inner);
922 let any_raw: *mut Box<dyn Any> = Box::into_raw(outer);
923
924 let (sexp, root) = unsafe { Self::create_extptr_sexp(any_raw) };
925 Self::from_owned_parts(sexp, unsafe { NonNull::new_unchecked(raw) }, root)
926 }
927
928 /// Constructs an ExternalPtr from a raw pointer, without thread checks.
929 ///
930 /// # Safety
931 ///
932 /// - `raw` must have been allocated via `Box::into_raw` or equivalent
933 /// - `raw` must not be null
934 /// - Caller transfers ownership to the ExternalPtr
935 /// - Must be called from R's main thread (no debug assertions)
936 #[inline]
937 pub unsafe fn from_raw_unchecked(raw: *mut T) -> Self {
938 let inner: Box<dyn Any> = unsafe { Box::from_raw(raw) };
939 let outer: Box<Box<dyn Any>> = Box::new(inner);
940 let any_raw: *mut Box<dyn Any> = Box::into_raw(outer);
941
942 let (sexp, root) = unsafe { Self::create_extptr_sexp_unchecked(any_raw) };
943 Self::from_owned_parts(sexp, unsafe { NonNull::new_unchecked(raw) }, root)
944 }
945
946 /// Consumes the ExternalPtr, returning a raw pointer.
947 ///
948 /// The caller is responsible for the memory, and the finalizer is
949 /// effectively orphaned (will do nothing since we clear the pointer).
950 ///
951 /// Equivalent to `Box::into_raw`.
952 #[inline]
953 pub fn into_raw(this: Self) -> *mut T {
954 let ptr = this.cached_ptr.as_ptr();
955
956 // Ownership of the R object leaves this handle: drop our GC root before
957 // `mem::forget` skips `Drop`. (`into_raw` already calls R API directly,
958 // so it is main-thread-contract — release directly, no thread hop.)
959 this.release_root_if_owned();
960
961 // Recover and disassemble the Box<Box<dyn Any>> wrapper.
962 // We need to free the wrapper allocations without dropping the T data.
963 let any_raw = unsafe { R_ExternalPtrAddr(this.sexp) as *mut Box<dyn Any> };
964
965 // Clear the external pointer so the finalizer becomes a no-op
966 unsafe { R_ClearExternalPtr(this.sexp) };
967
968 if !any_raw.is_null() {
969 // Reconstruct outer box → extract inner → leak inner (prevents T drop)
970 let outer: Box<Box<dyn Any>> = unsafe { Box::from_raw(any_raw) };
971 let inner: Box<dyn Any> = *outer;
972 // Box::into_raw leaks the inner allocation — caller owns T via `ptr`
973 let _ = Box::into_raw(inner);
974 }
975
976 // Don't run our Drop
977 mem::forget(this);
978
979 ptr
980 }
981
982 /// Consumes the ExternalPtr, returning a `NonNull` pointer.
983 ///
984 /// Equivalent to `Box::into_non_null`.
985 #[inline]
986 pub fn into_non_null(this: Self) -> NonNull<T> {
987 unsafe { NonNull::new_unchecked(Self::into_raw(this)) }
988 }
989
990 /// Consumes and leaks the ExternalPtr, returning a mutable reference.
991 ///
992 /// The memory will never be freed (from Rust's perspective; R's GC
993 /// finalizer is neutralized).
994 ///
995 /// Equivalent to `Box::leak`.
996 #[inline]
997 pub fn leak<'a>(this: Self) -> &'a mut T
998 where
999 T: 'a,
1000 {
1001 unsafe { &mut *Self::into_raw(this) }
1002 }
1003
1004 /// Consumes the ExternalPtr, returning the wrapped value.
1005 ///
1006 /// Uses `Box<dyn Any>::downcast` to recover the concrete `Box<T>`,
1007 /// then moves the value out.
1008 ///
1009 /// Equivalent to `*boxed` (deref move) or `Box::into_inner`.
1010 #[inline]
1011 pub fn into_inner(this: Self) -> T {
1012 // Ownership leaves this handle: drop our GC root before `mem::forget`.
1013 this.release_root_if_owned();
1014
1015 let any_raw = unsafe { R_ExternalPtrAddr(this.sexp) as *mut Box<dyn Any> };
1016
1017 // Clear so finalizer is no-op
1018 unsafe { R_ClearExternalPtr(this.sexp) };
1019 mem::forget(this);
1020
1021 assert!(!any_raw.is_null(), "ExternalPtr is null or cleared");
1022 let outer: Box<Box<dyn Any>> = unsafe { Box::from_raw(any_raw) };
1023 let inner: Box<dyn Any> = *outer;
1024 *inner
1025 .downcast::<T>()
1026 .expect("ExternalPtr type mismatch in into_inner")
1027 }
1028
1029 // region: Pin support (Box-equivalent)
1030
1031 /// Constructs a new `Pin<ExternalPtr<T>>`.
1032 ///
1033 /// Equivalent to `Box::pin`.
1034 ///
1035 /// # Note
1036 ///
1037 /// Unlike `Box::pin`, this requires `T: Unpin` because `ExternalPtr`
1038 /// implements `DerefMut` unconditionally. For `!Unpin` types, use
1039 /// `ExternalPtr::new` and manage pinning guarantees manually.
1040 #[inline]
1041 pub fn pin(x: T) -> Pin<Self>
1042 where
1043 T: Unpin,
1044 {
1045 // SAFETY: T: Unpin, so pinning is always safe
1046 Pin::new(Self::new(x))
1047 }
1048
1049 /// Constructs a new `Pin<ExternalPtr<T>>` without requiring `Unpin`.
1050 ///
1051 /// # Safety
1052 ///
1053 /// The caller must ensure that the pinning invariants are upheld:
1054 /// - The data will not be moved out of the `ExternalPtr`
1055 /// - The data will not be accessed mutably in ways that would move it
1056 ///
1057 /// Since `ExternalPtr` implements `DerefMut`, using this with `!Unpin`
1058 /// types requires careful handling to avoid moving the inner value.
1059 #[inline]
1060 pub fn pin_unchecked(x: T) -> Pin<Self> {
1061 unsafe { Pin::new_unchecked(Self::new(x)) }
1062 }
1063
1064 /// Converts a `ExternalPtr<T>` into a `Pin<ExternalPtr<T>>`.
1065 ///
1066 /// Equivalent to `Box::into_pin`.
1067 #[inline]
1068 pub fn into_pin(this: Self) -> Pin<Self>
1069 where
1070 T: Unpin,
1071 {
1072 // SAFETY: T: Unpin, so it's always safe to pin
1073 Pin::new(this)
1074 }
1075 // endregion
1076
1077 // region: Accessors
1078
1079 /// Returns a reference to the underlying value.
1080 ///
1081 /// Uses the cached pointer set at construction time, avoiding the
1082 /// `R_ExternalPtrAddr` FFI call on every access.
1083 #[inline]
1084 pub fn as_ref(&self) -> Option<&T> {
1085 // SAFETY: cached_ptr is always valid for the lifetime of ExternalPtr
1086 Some(unsafe { self.cached_ptr.as_ref() })
1087 }
1088
1089 /// Returns a mutable reference to the underlying value.
1090 ///
1091 /// Uses the cached pointer set at construction time, avoiding the
1092 /// `R_ExternalPtrAddr` FFI call on every access.
1093 #[inline]
1094 pub fn as_mut(&mut self) -> Option<&mut T> {
1095 // SAFETY: cached_ptr is always valid for the lifetime of ExternalPtr
1096 Some(unsafe { self.cached_ptr.as_mut() })
1097 }
1098
1099 /// Returns the raw pointer without consuming the ExternalPtr.
1100 #[inline]
1101 pub fn as_ptr(&self) -> *const T {
1102 self.cached_ptr.as_ptr().cast_const()
1103 }
1104
1105 /// Returns the raw mutable pointer without consuming the ExternalPtr.
1106 #[inline]
1107 pub fn as_mut_ptr(&mut self) -> *mut T {
1108 self.cached_ptr.as_ptr()
1109 }
1110
1111 /// Checks whether two `ExternalPtr`s refer to the same allocation (pointer identity).
1112 ///
1113 /// This ignores the pointee values. Use this when you need alias detection;
1114 /// prefer `PartialEq`/`PartialOrd` or `as_ref()` for value comparisons.
1115 #[inline]
1116 pub fn ptr_eq(this: &Self, other: &Self) -> bool {
1117 ptr::eq(
1118 this.cached_ptr.as_ptr().cast_const(),
1119 other.cached_ptr.as_ptr().cast_const(),
1120 )
1121 }
1122 // endregion
1123
1124 // region: R-specific accessors
1125
1126 /// Returns the underlying SEXP.
1127 ///
1128 /// # Warning
1129 ///
1130 /// The returned SEXP must not be duplicated or the finalizer will double-free.
1131 #[inline]
1132 pub fn as_sexp(&self) -> SEXP {
1133 self.sexp
1134 }
1135
1136 /// Create a lightweight alias of this ExternalPtr sharing the same R object.
1137 ///
1138 /// The returned `ExternalPtr` points to the **same** underlying EXTPTRSXP.
1139 /// No data is copied and no new R object is allocated -- both the original
1140 /// and the alias refer to the same R-level external pointer.
1141 ///
1142 /// This is the correct way to return "self" from a method that takes
1143 /// `self: &ExternalPtr<Self>`, preserving R object identity:
1144 ///
1145 /// ```ignore
1146 /// #[miniextendr(env)]
1147 /// impl MyType {
1148 /// pub fn identity(self: &ExternalPtr<Self>) -> ExternalPtr<Self> {
1149 /// self.reborrow()
1150 /// }
1151 /// }
1152 /// ```
1153 ///
1154 /// # Safety note
1155 ///
1156 /// The caller must not use the original and the alias to create overlapping
1157 /// mutable references (`as_mut`). In typical use (returning from a method),
1158 /// the borrow of the original ends when the method returns, so this is safe.
1159 #[inline]
1160 pub fn reborrow(&self) -> Self {
1161 // SAFETY: self.sexp is a valid live EXTPTRSXP that we already hold.
1162 // wrap_sexp re-extracts the data pointer from the same SEXP.
1163 unsafe { Self::wrap_sexp(self.sexp) }
1164 .expect("reborrow of live ExternalPtr should never fail")
1165 }
1166
1167 /// Returns the tag SEXP (type identifier symbol).
1168 #[inline]
1169 pub fn tag(&self) -> SEXP {
1170 unsafe { R_ExternalPtrTag(self.sexp) }
1171 }
1172
1173 /// Returns the tag SEXP (unchecked version).
1174 ///
1175 /// Skips thread safety checks for performance-critical paths.
1176 ///
1177 /// # Safety
1178 ///
1179 /// Must be called from the R main thread. Only use in ALTREP callbacks
1180 /// or other contexts where you're certain you're on the main thread.
1181 #[inline]
1182 pub unsafe fn tag_unchecked(&self) -> SEXP {
1183 unsafe { crate::sys::R_ExternalPtrTag_unchecked(self.sexp) }
1184 }
1185
1186 /// Returns the protected SEXP slot (user-protected objects).
1187 ///
1188 /// This returns the user-protected object stored in the prot VECSXP,
1189 /// not the VECSXP itself.
1190 #[inline]
1191 pub fn protected(&self) -> SEXP {
1192 unsafe {
1193 let prot = R_ExternalPtrProtected(self.sexp);
1194 if prot.is_null_or_nil() {
1195 return SEXP::nil();
1196 }
1197 if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
1198 return SEXP::nil();
1199 }
1200 prot.vector_elt(PROT_USER_INDEX)
1201 }
1202 }
1203
1204 /// Returns the protected SEXP slot (unchecked version).
1205 ///
1206 /// Skips thread safety checks for performance-critical paths.
1207 ///
1208 /// # Safety
1209 ///
1210 /// Must be called from the R main thread. Only use in ALTREP callbacks
1211 /// or other contexts where you're certain you're on the main thread.
1212 #[inline]
1213 pub unsafe fn protected_unchecked(&self) -> SEXP {
1214 use crate::sys::R_ExternalPtrProtected_unchecked;
1215
1216 unsafe {
1217 let prot = R_ExternalPtrProtected_unchecked(self.sexp);
1218 if prot.is_null_or_nil() {
1219 return SEXP::nil();
1220 }
1221 if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
1222 return SEXP::nil();
1223 }
1224 prot.vector_elt_unchecked(PROT_USER_INDEX)
1225 }
1226 }
1227
1228 /// Sets the user-protected SEXP slot.
1229 ///
1230 /// Use this to prevent R objects from being GC'd while this ExternalPtr exists.
1231 /// The type ID stored in prot slot 0 is preserved.
1232 ///
1233 /// Returns `false` if the prot structure is malformed (should not happen
1234 /// for ExternalPtrs created by this library).
1235 ///
1236 /// # Safety
1237 ///
1238 /// - `user_prot` must be a valid SEXP or R_NilValue
1239 /// - Must be called from the R main thread
1240 #[inline]
1241 pub unsafe fn set_protected(&self, user_prot: SEXP) -> bool {
1242 unsafe {
1243 let prot = R_ExternalPtrProtected(self.sexp);
1244 if prot.is_null_or_nil() {
1245 debug_assert!(false, "ExternalPtr prot slot is null or R_NilValue");
1246 return false;
1247 }
1248 if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
1249 debug_assert!(
1250 false,
1251 "ExternalPtr prot slot is not a VECSXP of expected length"
1252 );
1253 return false;
1254 }
1255 prot.set_vector_elt(PROT_USER_INDEX, user_prot);
1256 true
1257 }
1258 }
1259
1260 /// Returns the raw prot VECSXP (contains both type ID and user protected).
1261 ///
1262 /// Prefer using `protected()` for user data and `stored_type_id()` for type info.
1263 #[inline]
1264 pub fn prot_raw(&self) -> SEXP {
1265 unsafe { R_ExternalPtrProtected(self.sexp) }
1266 }
1267
1268 /// Checks if the internal pointer is null (already finalized or cleared).
1269 #[inline]
1270 pub fn is_null(&self) -> bool {
1271 unsafe { R_ExternalPtrAddr(self.sexp).is_null() }
1272 }
1273 // endregion
1274
1275 // region: Type checking
1276
1277 /// Attempt to wrap a SEXP as an ExternalPtr with type checking.
1278 ///
1279 /// Uses `Any::downcast_ref` for authoritative type checking (Rust `TypeId`).
1280 /// Type-erased `ExternalPtr<()>` deliberately skips the concrete downcast.
1281 ///
1282 /// Returns `None` if:
1283 /// - The internal pointer is null
1284 /// - The stored `Box<dyn Any>` does not contain a `T`
1285 ///
1286 /// # Safety
1287 ///
1288 /// - `sexp` must be a valid EXTPTRSXP created by this library
1289 /// - The caller must ensure no other ExternalPtr owns this SEXP
1290 pub unsafe fn wrap_sexp(sexp: SEXP) -> Option<Self> {
1291 debug_assert_eq!(
1292 sexp.type_of(),
1293 crate::SEXPTYPE::EXTPTRSXP,
1294 "wrap_sexp: expected EXTPTRSXP, got {:?}",
1295 sexp.type_of()
1296 );
1297 let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
1298 if any_raw.is_null() {
1299 return None;
1300 }
1301
1302 if is_type_erased::<T>() {
1303 // Type-erased path: skip downcast, just use the raw pointer
1304 // (ExternalPtr<()> doesn't care about the concrete type)
1305 return Some(Self::from_borrowed_parts(sexp, unsafe {
1306 NonNull::new_unchecked(any_raw.cast::<T>())
1307 }));
1308 }
1309
1310 // Use downcast_mut (not downcast_ref) so cached_ptr gets mutable
1311 // provenance — shared-reference provenance from downcast_ref would
1312 // make later writes through as_mut() UB under Stacked Borrows.
1313 let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
1314 let concrete: &mut T = any_box.downcast_mut::<T>()?;
1315
1316 Some(Self::from_borrowed_parts(sexp, unsafe {
1317 NonNull::new_unchecked(ptr::from_mut(concrete))
1318 }))
1319 }
1320
1321 /// Attempt to wrap a SEXP as an ExternalPtr (unchecked version).
1322 ///
1323 /// Skips thread safety checks for performance-critical paths like ALTREP callbacks.
1324 ///
1325 /// # Safety
1326 ///
1327 /// - `sexp` must be a valid EXTPTRSXP created by this library
1328 /// - The caller must ensure exclusive ownership
1329 /// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
1330 pub unsafe fn wrap_sexp_unchecked(sexp: SEXP) -> Option<Self> {
1331 use crate::sys::R_ExternalPtrAddr_unchecked;
1332
1333 debug_assert_eq!(
1334 sexp.type_of(),
1335 crate::SEXPTYPE::EXTPTRSXP,
1336 "wrap_sexp_unchecked: expected EXTPTRSXP, got {:?}",
1337 sexp.type_of()
1338 );
1339 let any_raw = unsafe { R_ExternalPtrAddr_unchecked(sexp) as *mut Box<dyn Any> };
1340 if any_raw.is_null() {
1341 return None;
1342 }
1343
1344 if is_type_erased::<T>() {
1345 return Some(Self::from_borrowed_parts(sexp, unsafe {
1346 NonNull::new_unchecked(any_raw.cast::<T>())
1347 }));
1348 }
1349
1350 let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
1351 let concrete: &mut T = any_box.downcast_mut::<T>()?;
1352
1353 Some(Self::from_borrowed_parts(sexp, unsafe {
1354 NonNull::new_unchecked(ptr::from_mut(concrete))
1355 }))
1356 }
1357
1358 /// Attempt to wrap a SEXP as an ExternalPtr, returning an error with type info on mismatch.
1359 ///
1360 /// This is used by the [`TryFromSexp`] trait implementation.
1361 ///
1362 /// # Safety
1363 ///
1364 /// Same as [`wrap_sexp`](Self::wrap_sexp).
1365 ///
1366 /// [`TryFromSexp`]: crate::TryFromSexp
1367 pub unsafe fn wrap_sexp_with_error(sexp: SEXP) -> Result<Self, TypeMismatchError> {
1368 debug_assert_eq!(
1369 sexp.type_of(),
1370 crate::SEXPTYPE::EXTPTRSXP,
1371 "wrap_sexp_with_error: expected EXTPTRSXP, got {:?}",
1372 sexp.type_of()
1373 );
1374 let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
1375 if any_raw.is_null() {
1376 return Err(TypeMismatchError::NullPointer);
1377 }
1378
1379 if is_type_erased::<T>() {
1380 return Ok(Self::from_borrowed_parts(sexp, unsafe {
1381 NonNull::new_unchecked(any_raw.cast::<T>())
1382 }));
1383 }
1384
1385 let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
1386 match any_box.downcast_mut::<T>() {
1387 Some(concrete) => Ok(Self::from_borrowed_parts(sexp, unsafe {
1388 NonNull::new_unchecked(ptr::from_mut(concrete))
1389 })),
1390 None => {
1391 // Try to get the stored type name from R symbol for error reporting
1392 let found = unsafe {
1393 let prot = R_ExternalPtrProtected(sexp);
1394 if !prot.is_null_or_nil()
1395 && prot.type_of() == SEXPTYPE::VECSXP
1396 && prot.len() >= PROT_VEC_LEN as usize
1397 {
1398 let stored_sym = prot.vector_elt(PROT_TYPE_ID_INDEX);
1399 if stored_sym.type_of() == SEXPTYPE::SYMSXP {
1400 symbol_name(stored_sym)
1401 } else {
1402 "<unknown>"
1403 }
1404 } else {
1405 "<unknown>"
1406 }
1407 };
1408 Err(TypeMismatchError::Mismatch {
1409 expected: T::TYPE_NAME,
1410 found,
1411 })
1412 }
1413 }
1414 }
1415
1416 /// Create an ExternalPtr from an SEXP without type checking.
1417 ///
1418 /// # Safety
1419 ///
1420 /// - `sexp` must be a valid EXTPTRSXP containing a `*mut Box<dyn Any>`
1421 /// wrapping a value of type `T`
1422 /// - The caller must ensure exclusive ownership
1423 #[inline]
1424 pub unsafe fn from_sexp_unchecked(sexp: SEXP) -> Self {
1425 debug_assert_eq!(
1426 sexp.type_of(),
1427 crate::SEXPTYPE::EXTPTRSXP,
1428 "from_sexp_unchecked: expected EXTPTRSXP, got {:?}",
1429 sexp.type_of()
1430 );
1431 let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
1432 debug_assert!(!any_raw.is_null(), "from_sexp_unchecked: null pointer");
1433
1434 let cached_ptr = if is_type_erased::<T>() {
1435 unsafe { NonNull::new_unchecked(any_raw.cast::<T>()) }
1436 } else {
1437 let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
1438 let concrete: &mut T = unsafe { any_box.downcast_mut::<T>().unwrap_unchecked() };
1439 unsafe { NonNull::new_unchecked(ptr::from_mut(concrete)) }
1440 };
1441
1442 Self::from_borrowed_parts(sexp, cached_ptr)
1443 }
1444 // endregion
1445
1446 // region: Downcast support
1447
1448 /// Returns the type name for type T.
1449 #[inline]
1450 pub fn type_name() -> &'static str {
1451 T::TYPE_NAME
1452 }
1453
1454 /// Returns the type name stored in this ExternalPtr's prot slot.
1455 ///
1456 /// Returns `None` if the prot slot doesn't contain a valid type symbol.
1457 #[inline]
1458 pub fn stored_type_name(&self) -> Option<&'static str> {
1459 unsafe {
1460 let prot = R_ExternalPtrProtected(self.sexp);
1461 if prot.is_null_or_nil() {
1462 return None;
1463 }
1464 if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
1465 return None;
1466 }
1467 let stored_sym = prot.vector_elt(PROT_TYPE_ID_INDEX);
1468 if stored_sym.type_of() != SEXPTYPE::SYMSXP {
1469 return None;
1470 }
1471 Some(symbol_name(stored_sym))
1472 }
1473 }
1474 // endregion
1475}
1476
1477impl ExternalPtr<()> {
1478 /// Create a type-erased ExternalPtr from an EXTPTRSXP without checking the stored type.
1479 ///
1480 /// # Safety
1481 ///
1482 /// - `sexp` must be a valid EXTPTRSXP
1483 /// - Caller must ensure exclusive ownership semantics are upheld
1484 #[inline]
1485 pub unsafe fn from_sexp(sexp: SEXP) -> Self {
1486 debug_assert!(sexp.type_of() == SEXPTYPE::EXTPTRSXP);
1487 unsafe { Self::from_sexp_unchecked(sexp) }
1488 }
1489
1490 /// Check whether the stored `Box<dyn Any>` contains a `T`.
1491 ///
1492 /// Uses `Any::is` for authoritative runtime type checking.
1493 #[inline]
1494 pub fn is<T: TypedExternal>(&self) -> bool {
1495 let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
1496 if any_raw.is_null() {
1497 return false;
1498 }
1499 let any_box: &Box<dyn Any> = unsafe { &*any_raw };
1500 any_box.is::<T>()
1501 }
1502
1503 /// Downcast to an immutable reference of the stored type if it matches `T`.
1504 ///
1505 /// Uses `Any::downcast_ref` for authoritative runtime type checking.
1506 #[inline]
1507 pub fn downcast_ref<T: TypedExternal>(&self) -> Option<&T> {
1508 let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
1509 if any_raw.is_null() {
1510 return None;
1511 }
1512 let any_box: &Box<dyn Any> = unsafe { &*any_raw };
1513 any_box.downcast_ref::<T>()
1514 }
1515
1516 /// Downcast to a mutable reference of the stored type if it matches `T`.
1517 ///
1518 /// Uses `Any::downcast_mut` for authoritative runtime type checking.
1519 #[inline]
1520 pub fn downcast_mut<T: TypedExternal>(&mut self) -> Option<&mut T> {
1521 let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
1522 if any_raw.is_null() {
1523 return None;
1524 }
1525 let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
1526 any_box.downcast_mut::<T>()
1527 }
1528}
1529
1530/// Error returned when type checking fails in `try_from_sexp_with_error`.
1531///
1532/// The `found` field in `Mismatch` contains a `&'static str` from R's
1533/// interned symbol table, which persists for the R session lifetime.
1534#[derive(Debug, Clone)]
1535pub enum TypeMismatchError {
1536 /// The external pointer's address was null.
1537 NullPointer,
1538 /// The prot slot didn't contain a valid type symbol.
1539 InvalidTypeId,
1540 /// The stored type doesn't match the expected type.
1541 Mismatch {
1542 /// Expected Rust type name from this pointer wrapper.
1543 expected: &'static str,
1544 /// Actual stored Rust type name found in pointer metadata.
1545 found: &'static str,
1546 },
1547}
1548
1549impl fmt::Display for TypeMismatchError {
1550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1551 match self {
1552 Self::NullPointer => write!(f, "external pointer is null"),
1553 Self::InvalidTypeId => write!(f, "external pointer has no valid type id"),
1554 Self::Mismatch { expected, found } => {
1555 write!(
1556 f,
1557 "type mismatch: expected `{}`, found `{}`",
1558 expected, found
1559 )
1560 }
1561 }
1562 }
1563}
1564
1565impl std::error::Error for TypeMismatchError {}
1566// endregion
1567
1568// region: MaybeUninit support
1569
1570// We need a separate TypedExternal impl for MaybeUninit<T>
1571// This is typically done via blanket impl or macro
1572
1573impl<T: TypedExternal> ExternalPtr<MaybeUninit<T>>
1574where
1575 MaybeUninit<T>: TypedExternal,
1576{
1577 /// Converts to `ExternalPtr<T>`.
1578 ///
1579 /// # Safety
1580 ///
1581 /// The value must have been initialized.
1582 ///
1583 /// # Implementation Note
1584 ///
1585 /// This method creates a *new* SEXP with `T`'s type information, leaving
1586 /// the original `MaybeUninit<T>` SEXP as an orphaned empty shell in R's heap.
1587 /// This is necessary because the type ID stored in the prot slot must match
1588 /// the actual type. The orphaned SEXP will be cleaned up by R's GC eventually.
1589 ///
1590 /// If you need to avoid this overhead, consider using `ExternalPtr<T>::new`
1591 /// directly and initializing in place via `as_mut`.
1592 ///
1593 /// Equivalent to `Box::assume_init`.
1594 #[inline]
1595 pub fn assume_init(self) -> ExternalPtr<T> {
1596 // Get the raw pointer (this clears the original SEXP, making its finalizer a no-op)
1597 let ptr = Self::into_raw(self).cast();
1598
1599 // Create a new ExternalPtr with T's type info
1600 unsafe { ExternalPtr::from_raw(ptr) }
1601 }
1602
1603 /// Writes a value and converts to initialized.
1604 ///
1605 /// Creates a new SEXP with `T`'s type information (the original
1606 /// `MaybeUninit<T>` SEXP becomes an orphaned shell, cleaned up by GC).
1607 #[inline]
1608 pub fn write(mut self, value: T) -> ExternalPtr<T> {
1609 unsafe {
1610 (*Self::as_mut_ptr(&mut self)).write(value);
1611 self.assume_init()
1612 }
1613 }
1614}
1615/// Type-erased `ExternalPtr` for cases where the concrete `T` is not needed.
1616pub type ErasedExternalPtr = ExternalPtr<()>;
1617// endregion
1618
1619// region: Trait Implementations
1620
1621impl<T: TypedExternal> Deref for ExternalPtr<T> {
1622 type Target = T;
1623
1624 #[inline]
1625 fn deref(&self) -> &T {
1626 Self::as_ref(self).expect("ExternalPtr is null or cleared")
1627 }
1628}
1629
1630impl<T: TypedExternal> DerefMut for ExternalPtr<T> {
1631 #[inline]
1632 fn deref_mut(&mut self) -> &mut T {
1633 Self::as_mut(self).expect("ExternalPtr is null or cleared")
1634 }
1635}
1636
1637impl<T: TypedExternal> AsRef<T> for ExternalPtr<T> {
1638 #[inline]
1639 fn as_ref(&self) -> &T {
1640 Self::as_ref(self).expect("ExternalPtr is null or cleared")
1641 }
1642}
1643
1644impl<T: TypedExternal> AsMut<T> for ExternalPtr<T> {
1645 #[inline]
1646 fn as_mut(&mut self) -> &mut T {
1647 Self::as_mut(self).expect("ExternalPtr is null or cleared")
1648 }
1649}
1650
1651impl<T: TypedExternal> std::borrow::Borrow<T> for ExternalPtr<T> {
1652 #[inline]
1653 fn borrow(&self) -> &T {
1654 Self::as_ref(self).expect("ExternalPtr is null or cleared")
1655 }
1656}
1657
1658impl<T: TypedExternal> std::borrow::BorrowMut<T> for ExternalPtr<T> {
1659 #[inline]
1660 fn borrow_mut(&mut self) -> &mut T {
1661 Self::as_mut(self).expect("ExternalPtr is null or cleared")
1662 }
1663}
1664
1665impl<T: TypedExternal + Clone> Clone for ExternalPtr<T> {
1666 /// Deep clones the inner value into a new ExternalPtr.
1667 ///
1668 /// This creates a completely independent ExternalPtr with its own
1669 /// heap allocation and finalizer.
1670 #[inline]
1671 fn clone(&self) -> Self {
1672 Self::new((**self).clone())
1673 }
1674
1675 #[inline]
1676 fn clone_from(&mut self, source: &Self) {
1677 (**self).clone_from(&**source);
1678 }
1679}
1680
1681impl<T: TypedExternal + Default> Default for ExternalPtr<T> {
1682 /// Creates an ExternalPtr containing the default value of T.
1683 #[inline]
1684 fn default() -> Self {
1685 Self::new(T::default())
1686 }
1687}
1688
1689impl<T: TypedExternal + fmt::Debug> fmt::Debug for ExternalPtr<T> {
1690 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1691 fmt::Debug::fmt(&**self, f)
1692 }
1693}
1694
1695impl<T: TypedExternal + fmt::Display> fmt::Display for ExternalPtr<T> {
1696 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1697 fmt::Display::fmt(&**self, f)
1698 }
1699}
1700
1701impl<T: TypedExternal> fmt::Pointer for ExternalPtr<T> {
1702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1703 fmt::Pointer::fmt(&Self::as_ptr(self), f)
1704 }
1705}
1706
1707impl<T: TypedExternal + PartialEq> PartialEq for ExternalPtr<T> {
1708 #[inline]
1709 fn eq(&self, other: &Self) -> bool {
1710 **self == **other
1711 }
1712}
1713
1714impl<T: TypedExternal + Eq> Eq for ExternalPtr<T> {}
1715
1716impl<T: TypedExternal + PartialOrd> PartialOrd for ExternalPtr<T> {
1717 #[inline]
1718 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1719 (**self).partial_cmp(&**other)
1720 }
1721}
1722
1723impl<T: TypedExternal + Ord> Ord for ExternalPtr<T> {
1724 #[inline]
1725 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1726 (**self).cmp(&**other)
1727 }
1728}
1729
1730impl<T: TypedExternal + Hash> Hash for ExternalPtr<T> {
1731 #[inline]
1732 fn hash<H: Hasher>(&self, state: &mut H) {
1733 (**self).hash(state);
1734 }
1735}
1736
1737impl<T: TypedExternal + std::iter::Iterator> std::iter::Iterator for ExternalPtr<T> {
1738 type Item = T::Item;
1739
1740 fn next(&mut self) -> Option<Self::Item> {
1741 (**self).next()
1742 }
1743
1744 fn size_hint(&self) -> (usize, Option<usize>) {
1745 (**self).size_hint()
1746 }
1747
1748 fn nth(&mut self, n: usize) -> Option<Self::Item> {
1749 (**self).nth(n)
1750 }
1751}
1752
1753impl<T: TypedExternal + std::iter::DoubleEndedIterator> std::iter::DoubleEndedIterator
1754 for ExternalPtr<T>
1755{
1756 fn next_back(&mut self) -> Option<Self::Item> {
1757 (**self).next_back()
1758 }
1759
1760 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
1761 (**self).nth_back(n)
1762 }
1763}
1764
1765impl<T: TypedExternal + std::iter::ExactSizeIterator> std::iter::ExactSizeIterator
1766 for ExternalPtr<T>
1767{
1768 fn len(&self) -> usize {
1769 (**self).len()
1770 }
1771}
1772
1773impl<T: TypedExternal + std::iter::FusedIterator> std::iter::FusedIterator for ExternalPtr<T> {}
1774
1775impl<T: TypedExternal> From<T> for ExternalPtr<T> {
1776 #[inline]
1777 fn from(t: T) -> Self {
1778 Self::new(t)
1779 }
1780}
1781
1782impl<T: TypedExternal> From<Box<T>> for ExternalPtr<T> {
1783 #[inline]
1784 fn from(boxed: Box<T>) -> Self {
1785 unsafe { Self::from_raw(Box::into_raw(boxed)) }
1786 }
1787}
1788
1789// `Drop` releases the R-side GC root taken at construction (for *owning*
1790// handles only) but never frees the pointee — that stays R's job, run by the
1791// `release_any` finalizer when R garbage-collects the `EXTPTRSXP`. Dropping the
1792// root just makes the object eligible for collection once R itself holds no
1793// other reference; if R still references it (the usual case — it was returned
1794// from a `.Call` or stored), it stays alive and the finalizer runs later.
1795//
1796// For deterministic *value* cleanup, use `ExternalPtr::into_inner` (moves the
1797// value out) or `drop(Box::from_raw(ExternalPtr::into_raw(ptr)))`.
1798impl<T: TypedExternal> Drop for ExternalPtr<T> {
1799 fn drop(&mut self) {
1800 self.release_root_if_owned();
1801 }
1802}
1803// endregion
1804
1805// region: Finalizer
1806
1807/// Guard that aborts the process if dropped while a panic is in progress.
1808///
1809/// Used by [`drop_catching_panic`] to implement panic-safe destructor calls
1810/// without `catch_unwind`. When `f()` completes normally, the guard is
1811/// dropped with `std::thread::panicking() == false` and becomes a no-op.
1812/// If `f()` panics, the guard's destructor runs during stack unwinding
1813/// (when `std::thread::panicking() == true`) and calls `process::abort()`.
1814///
1815/// This approach avoids `catch_unwind`, which registers LLVM unwind landing
1816/// pads. Inside R's GC finalizer walk, any interaction with the unwinding
1817/// machinery — especially on the first call that lazily initialises exception
1818/// handling state — can trigger an allocator call that re-enters R's GC and
1819/// produces a "recursive gc invocation" hard crash.
1820#[must_use]
1821struct AbortIfUnwinding;
1822
1823impl Drop for AbortIfUnwinding {
1824 #[cold]
1825 fn drop(&mut self) {
1826 if std::thread::panicking() {
1827 // A panic propagated through a finalizer — abort immediately.
1828 // The value being dropped is in an indeterminate state; continuing
1829 // is not safe.
1830 eprintln!("miniextendr: destructor panicked during R finalization; aborting");
1831 std::process::abort();
1832 }
1833 }
1834}
1835
1836/// Run a destructor closure, aborting the process if the closure panics.
1837///
1838/// A panic inside a GC finalizer cannot be safely propagated: the finalizer
1839/// runs at an arbitrary point in R's garbage collector, and unwinding across
1840/// the C-ABI boundary into R's runtime is undefined behaviour. Aborting is
1841/// the only safe recovery strategy — the destructor has already left the
1842/// value in an indeterminate state, so continuing is not an option.
1843///
1844/// ## Implementation note
1845///
1846/// This function deliberately avoids `std::panic::catch_unwind`. On the first
1847/// call from within R's GC finalizer, `catch_unwind` may lazily initialise
1848/// LLVM exception-handling state, which can allocate. Any allocation during a
1849/// GC finalizer re-enters the GC and triggers the fatal "recursive gc
1850/// invocation" crash. Instead, this function uses a drop-guard whose `Drop`
1851/// impl calls `std::thread::panicking()` — a cheap, allocation-free TLS read.
1852///
1853/// This helper is `#[doc(hidden)]` because it is called from macro-generated
1854/// code and is not part of the public API.
1855#[doc(hidden)]
1856#[inline]
1857pub fn drop_catching_panic<F: FnOnce()>(f: F) {
1858 let _guard = AbortIfUnwinding;
1859 f();
1860 // guard dropped here with panicking() == false → no-op
1861}
1862
1863/// Non-generic C finalizer called by R's garbage collector.
1864///
1865/// Since `ExternalPtr` stores `Box<Box<dyn Any>>`, the `Any` vtable carries
1866/// the concrete type's drop function. No generic parameter needed — one
1867/// finalizer function handles all `ExternalPtr<T>` types.
1868extern "C-unwind" fn release_any(sexp: SEXP) {
1869 if sexp.is_null() {
1870 return;
1871 }
1872 if sexp.is_nil() {
1873 return;
1874 }
1875
1876 let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
1877
1878 // Guard against double-finalization
1879 if any_raw.is_null() {
1880 return;
1881 }
1882
1883 // Clear the external pointer first (prevents double-free if called again)
1884 unsafe { R_ClearExternalPtr(sexp) };
1885
1886 // Reconstruct the outer Box<Box<dyn Any>> and let it drop.
1887 // This drops the outer Box, then the inner Box<dyn Any>, which
1888 // uses the vtable to drop the concrete T value.
1889 //
1890 // A panicking Drop impl must not unwind across the C-ABI boundary into R.
1891 // `drop_catching_panic` catches any panic and aborts instead.
1892 drop_catching_panic(|| drop(unsafe { Box::from_raw(any_raw) }));
1893}
1894// endregion
1895
1896// region: Utility: ExternalSlice (helper for slice data)
1897
1898/// A slice stored as a standalone struct, suitable for wrapping in ExternalPtr.
1899///
1900/// This is analogous to the data inside a `Box<[T]>`, but stores capacity
1901/// for proper deallocation when created from a `Vec`.
1902///
1903/// # Usage
1904///
1905/// To use with `ExternalPtr`, implement `TypedExternal` for your specific
1906/// `ExternalSlice<YourType>`:
1907///
1908/// ```ignore
1909/// impl_typed_external!(ExternalSlice<MyElement>);
1910/// let ptr = ExternalPtr::new(ExternalSlice::new(vec![1, 2, 3]));
1911/// ```
1912#[repr(C)]
1913pub struct ExternalSlice<T: 'static> {
1914 ptr: NonNull<T>,
1915 len: usize,
1916 capacity: usize,
1917}
1918
1919impl<T: 'static> ExternalSlice<T> {
1920 /// Create an external slice from a `Vec`, preserving its allocation.
1921 pub fn new(slice: Vec<T>) -> Self {
1922 let mut vec = ManuallyDrop::new(slice);
1923 Self {
1924 ptr: unsafe { NonNull::new_unchecked(vec.as_mut_ptr()) },
1925 len: vec.len(),
1926 capacity: vec.capacity(),
1927 }
1928 }
1929
1930 /// Create from a boxed slice (capacity == len).
1931 pub fn from_boxed(boxed: Box<[T]>) -> Self {
1932 let len = boxed.len();
1933 let ptr = Box::into_raw(boxed).cast();
1934 Self {
1935 ptr: unsafe { NonNull::new_unchecked(ptr) },
1936 len,
1937 capacity: len,
1938 }
1939 }
1940
1941 /// Borrow the contents as a shared slice.
1942 pub fn as_slice(&self) -> &[T] {
1943 unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
1944 }
1945
1946 /// Borrow the contents as a mutable slice.
1947 pub fn as_mut_slice(&mut self) -> &mut [T] {
1948 unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
1949 }
1950
1951 /// Number of elements in the slice.
1952 pub fn len(&self) -> usize {
1953 self.len
1954 }
1955
1956 /// Returns true if the slice is empty.
1957 pub fn is_empty(&self) -> bool {
1958 self.len == 0
1959 }
1960
1961 /// Capacity of the underlying allocation.
1962 pub fn capacity(&self) -> usize {
1963 self.capacity
1964 }
1965}
1966
1967impl<T: 'static> Drop for ExternalSlice<T> {
1968 fn drop(&mut self) {
1969 unsafe {
1970 let _ = Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.capacity);
1971 }
1972 }
1973}
1974// endregion
1975
1976mod altrep_helpers;
1977pub use altrep_helpers::*;
1978
1979#[cfg(test)]
1980mod tests {
1981 use super::drop_catching_panic;
1982
1983 #[test]
1984 fn drop_catching_panic_does_not_propagate_panic() {
1985 // Verify that drop_catching_panic catches a panicking closure and does
1986 // NOT propagate the panic to the caller.
1987 //
1988 // Note: we cannot test the abort path from inside a test process, so
1989 // we document it with a comment instead:
1990 // If the closure panics, `drop_catching_panic` calls `eprintln!` then
1991 // `std::process::abort()`. That path is exercised only by the process
1992 // dying, which is observable from an external test harness (not done
1993 // here to keep CI simple).
1994 //
1995 // What we CAN test: the happy path (no panic) completes normally, and
1996 // the function compiles and links correctly with a `FnOnce()` generic.
1997 let mut ran = false;
1998 drop_catching_panic(|| {
1999 ran = true;
2000 });
2001 assert!(ran, "closure should have been called");
2002 }
2003
2004 #[test]
2005 fn drop_catching_panic_happy_path_drops_value() {
2006 // Confirm that the closure's side-effects (i.e. actual drop) occur
2007 // when no panic is raised.
2008 use std::sync::Arc;
2009 use std::sync::atomic::{AtomicBool, Ordering};
2010
2011 let dropped = Arc::new(AtomicBool::new(false));
2012 let flag = dropped.clone();
2013
2014 struct DropSignal(Arc<AtomicBool>);
2015 impl Drop for DropSignal {
2016 fn drop(&mut self) {
2017 self.0.store(true, Ordering::SeqCst);
2018 }
2019 }
2020
2021 let signal = DropSignal(flag);
2022 drop_catching_panic(|| drop(signal));
2023
2024 assert!(
2025 dropped.load(Ordering::SeqCst),
2026 "inner value should have been dropped"
2027 );
2028 }
2029}