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}