Skip to main content

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