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`] — 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}