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 + the `RSidecar` and `Computed` marker types |
12//! | [`sidecar`] | [`Sidecar<T>`] fields: typed values kept in the `prot` list |
13//!
14//! # Core Types
15//!
16//! - [`ExternalPtr<T>`] — owned pointer wrapping EXTPTRSXP
17//! - [`TypedExternal`] — display and diagnostic metadata for stored types
18//! - [`ExternalSlice<T>`] — helper for slice data in external pointers
19//! - [`ErasedExternalPtr`] — type-erased `ExternalPtr<()>` alias
20//! - [`IntoExternalPtr`] — conversion trait for wrapping values
21//!
22//! `PartialEq`/`PartialOrd` compare the pointee values (like `Box<T>`),
23//! borrowing both. Use `ptr_eq` when you care about pointer identity.
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) | Until released or the pool drops | Any order | Values a struct owns; many values; cross-call roots |
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) kept alive by a struct, or 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//! / `same_object`) 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 `Any` value is a [`RefCell<T>`], whose
96//! borrow flag is what generated method wrappers, field accessors and trait
97//! shims check before they make a `&T` / `&mut T` (see "Reentrant calls"
98//! below). The outer `Box` keeps the heap address stable so
99//! [`ExternalPtr::cached_ptr`](struct@ExternalPtr) can be cached once at
100//! construction.
101//!
102//! The `tag` slot holds a symbol (type name, for display).
103//! The `prot` slot holds a VECSXP (list) of `2 + T::R_SLOT_COUNT` elements:
104//!   - Index 0: SYMSXP (interned type ID symbol, for error messages)
105//!   - Index 1: User-protected SEXP slot (for preventing GC of R objects)
106//!   - Index 2..: one value per [`Sidecar<T>`] field of `T`
107//!
108//! Before reading the address as Rust storage, checked conversions and erased
109//! handle accessors require this metadata shape (including a non-ALTREP list).
110//! Trait-ABI pointers carry a reserved tag and a different allocation layout;
111//! they are rejected here. These checks reject ordinary foreign pointers, not
112//! foreign addresses deliberately forged with miniextendr metadata. Symbol
113//! names never establish the concrete Rust type; `Any::downcast` still does.
114//!
115//! ## `TYPE_NAME_CSTR` vs `TYPE_ID_CSTR`
116//!
117//! [`TypedExternal`] exposes two associated constants with distinct roles —
118//! mixing them up does not break type safety (`Any::downcast` is the real
119//! gate) but produces noisy diagnostics.
120//!
121//! | Constant | Role | Visible to R as | Authoritative? |
122//! |---|---|---|---|
123//! | `TYPE_NAME_CSTR` | Display tag | `class()` / `print()` | No |
124//! | `TYPE_ID_CSTR` | Error-message identifier on downcast failure | Stored in `prot[0]` | No (cosmetic; downcast uses `TypeId`) |
125//!
126//! `#[derive(ExternalPtr)]` fills both with sensible defaults; only override
127//! manually when implementing `TypedExternal` by hand.
128//!
129//! # Pointer provenance for `cached_ptr`
130//!
131//! `ExternalPtr` caches the address of the value's [`RefCell<T>`] at
132//! construction so [`borrow`](ExternalPtr::borrow) /
133//! [`borrow_mut`](ExternalPtr::borrow_mut) avoid an FFI call on every access.
134//! A `RefCell` hands out `&mut T` from a shared reference to the cell, so a
135//! pointer cached from `downcast_ref::<RefCell<T>>()` is as good for writes
136//! as one from `Box::into_raw`.
137//!
138//! # Reentrant calls
139//!
140//! R reaches a Rust value only through a generated wrapper, on R's main
141//! thread, and the wrapper borrows the value's [`RefCell`] for the whole call:
142//! shared for `&self` methods, `&T` arguments, [`ExternalPtrRef`] receivers
143//! and arguments, and field getters; exclusive for `&mut self` and `self`
144//! methods, `&mut T` arguments, [`ExternalPtrMut`] receivers and arguments,
145//! and field setters. The body gets plain references, which Rust's borrow
146//! checker covers from there. A second borrow during the call, from the same
147//! object passed twice with a `&mut T`, or from R code the body calls back
148//! into (an R function argument, `RCall`) reaching the object again, raises
149//! an R error of class `miniextendr_borrow_conflict` instead of making a
150//! second reference. A borrow taken through a trait view lives in the
151//! producer's shim, so views in other packages see it too.
152//!
153//! An `ExternalPtr<T>` handle, as an argument, a `self: ExternalPtr<Self>`
154//! receiver, a `Vec` element or a value Rust keeps, takes no borrow until it
155//! is read: [`borrow`](ExternalPtr::borrow) and
156//! [`borrow_mut`](ExternalPtr::borrow_mut) check the `RefCell` and raise the
157//! same error. A handle has no `Deref`, so every read through it is one of
158//! these. `into_inner` and the other consuming functions refuse a value
159//! something still borrows, with the same error.
160//!
161//! The borrow is released when the wrapper returns, panics, or unwinds an R
162//! error raised through [`RCall::eval_with_handlers`](crate::expression::RCall::eval_with_handlers)
163//! (or any API that turns an R error into a Rust unwind). A bare `Rf_eval`
164//! that raises an R error jumps over the wrapper's frames, so the object
165//! stays borrowed for the rest of the session.
166//!
167//! # See also
168//!
169//! - [`crate::altrep`] — when the alternative (an ALTREP class) makes more
170//!   sense than `ExternalPtr`.
171//!
172//! # ExternalPtr is Not an R Native Type
173//!
174//! Unlike R's native atomic types (`integer`, `double`, `character`, etc.),
175//! external pointers cannot be coerced to vectors or used in R's vectorized
176//! operations. This is an R limitation, not a miniextendr limitation:
177//!
178//! ```r
179//! > matrix(new("externalptr"), 1, 1)
180//! Error in `as.vector()`:
181//! ! cannot coerce type 'externalptr' to vector of type 'any'
182//! ```
183//!
184//! If you need your Rust type to participate in R's vector/matrix operations,
185//! consider implementing [`IntoList`](crate::list::IntoList) (via `#[derive(IntoList)]`)
186//! to convert your struct to a named R list, or use ALTREP to expose Rust
187//! iterators as lazy R vectors.
188
189use std::any::Any;
190use std::any::TypeId;
191use std::cell::{Ref, RefCell, RefMut};
192use std::fmt;
193use std::hash::{Hash, Hasher};
194use std::marker::PhantomData;
195use std::mem::{self, ManuallyDrop, MaybeUninit};
196use std::ptr::{self, NonNull};
197
198use crate::RValue;
199use crate::condition::{ConditionCall, RCondition};
200use crate::gc_protect::ProtectScope;
201use crate::protect_pool::{ProtectKey, ProtectPool};
202use crate::sys::{
203    R_ClearExternalPtr, R_ExternalPtrAddr, R_ExternalPtrProtected, R_ExternalPtrTag,
204    R_MakeExternalPtr, R_MakeExternalPtr_unchecked, R_RegisterCFinalizerEx,
205    R_RegisterCFinalizerEx_unchecked, R_SetExternalPtrAddr, R_SetExternalPtrAddr_unchecked,
206    R_SetExternalPtrProtected, R_SetExternalPtrTag, Rf_allocVector, Rf_allocVector_unchecked,
207    Rf_install, Rf_install_unchecked,
208};
209use crate::{R_xlen_t, Rboolean, SEXP, SEXPTYPE, SexpExt};
210
211/// Own the allocation during the handoff to R's main thread.
212///
213/// # Safety
214///
215/// The value is exclusively owned and accessed only after the handoff. Keeping
216/// the box owned also reclaims it if routing rejects the calling context.
217type SendableAnyPtr = crate::worker::Sendable<Box<Box<dyn Any>>>;
218
219/// Create a new sendable pointer from a raw `*mut Box<dyn Any>`.
220///
221/// # Safety
222///
223/// The pointer must be a live `Box::into_raw` allocation; ownership transfers.
224#[inline]
225unsafe fn sendable_any_ptr_new(ptr: *mut Box<dyn Any>) -> SendableAnyPtr {
226    crate::worker::Sendable(unsafe { Box::from_raw(ptr) })
227}
228
229/// Get the raw pointer, consuming the sendable wrapper.
230#[inline]
231fn sendable_any_ptr_into_ptr(ptr: SendableAnyPtr) -> *mut Box<dyn Any> {
232    Box::into_raw(ptr.0)
233}
234
235/// Index of the type SYMSXP contained in the `prot` (a `VECSXP` list)
236const PROT_TYPE_ID_INDEX: isize = 0;
237/// Index of user-protected objects contained in the `prot` (a `VECSXP` list)
238const PROT_USER_INDEX: isize = 1;
239/// Length of the `prot` list (`VECSXP`) before the [`Sidecar<T>`] entries,
240/// which start at this index
241const PROT_VEC_LEN: isize = 2;
242
243/// Length of `T`'s `prot` list: the type ID, the user slot and one entry per
244/// [`Sidecar<T>`] field.
245#[inline]
246fn prot_vec_len<T: TypedExternal>() -> R_xlen_t {
247    let r_slots = R_xlen_t::try_from(T::R_SLOT_COUNT).expect("sidecar count exceeds R_xlen_t::MAX");
248    PROT_VEC_LEN + r_slots
249}
250
251#[inline]
252fn is_type_erased<T: 'static>() -> bool {
253    TypeId::of::<T>() == TypeId::of::<()>()
254}
255
256/// Get the interned R symbol for a type's name.
257///
258/// R interns symbols via `Rf_install`, so the same string always returns the
259/// same pointer. The symbol is stable display metadata; `Any::downcast` is the
260/// authoritative type check.
261///
262/// # Safety
263///
264/// Must be called from R's main thread.
265#[inline]
266unsafe fn type_symbol<T: TypedExternal>() -> SEXP {
267    unsafe { Rf_install(T::TYPE_NAME_CSTR.as_ptr().cast()) }
268}
269
270/// Unchecked version of [`type_symbol`] - no thread safety checks.
271///
272/// # Safety
273///
274/// Must be called from R's main thread. No debug assertions.
275#[inline]
276unsafe fn type_symbol_unchecked<T: TypedExternal>() -> SEXP {
277    unsafe { Rf_install_unchecked(T::TYPE_NAME_CSTR.as_ptr().cast()) }
278}
279
280/// Get the namespaced type ID symbol used in diagnostics.
281///
282/// Uses `TYPE_ID_CSTR`, which includes the module path to make mismatch
283/// messages unambiguous.
284///
285/// # Safety
286///
287/// Must be called from R's main thread.
288#[inline]
289unsafe fn type_id_symbol<T: TypedExternal>() -> SEXP {
290    unsafe { Rf_install(T::TYPE_ID_CSTR.as_ptr().cast()) }
291}
292
293/// Unchecked version of [`type_id_symbol`].
294///
295/// # Safety
296///
297/// Must be called from R's main thread. No debug assertions.
298#[inline]
299unsafe fn type_id_symbol_unchecked<T: TypedExternal>() -> SEXP {
300    unsafe { Rf_install_unchecked(T::TYPE_ID_CSTR.as_ptr().cast()) }
301}
302
303/// Get the type name from a stored symbol SEXP.
304///
305/// # Safety
306///
307/// `sym` must be a valid SYMSXP.
308#[inline]
309fn symbol_name(sym: SEXP) -> &'static str {
310    use crate::SexpExt;
311    let printname = sym.printname();
312    let cstr = printname.r_char();
313    let len = printname.len();
314    unsafe {
315        std::str::from_utf8(std::slice::from_raw_parts(cstr.cast(), len))
316            .expect("R SYMSXP PRINTNAME is not valid UTF-8")
317    }
318}
319
320/// The type-ID symbol an `ExternalPtr`-built pointer keeps in `prot[0]`, or
321/// `None` when its metadata is not the shape ordinary `ExternalPtr` builds.
322/// This also excludes trait-ABI storage, which has a different Rust layout.
323///
324/// Only inspect non-ALTREP metadata: checking ownership must not invoke a
325/// foreign callback. A foreign address forged with this metadata is outside
326/// the trust boundary; after this check `Any` still decides the concrete type.
327///
328/// # Safety
329///
330/// Must be called from R's main thread with a valid `EXTPTRSXP`.
331#[inline]
332unsafe fn stored_type_id_symbol(sexp: SEXP) -> Option<SEXP> {
333    let tag = unsafe { R_ExternalPtrTag(sexp) };
334    // Symbols are interned, so the trait-ABI tag compares by pointer.
335    if tag.type_of() != SEXPTYPE::SYMSXP || tag == unsafe { crate::mx_abi::get_tag() } {
336        return None;
337    }
338    let prot = unsafe { R_ExternalPtrProtected(sexp) };
339    if prot.type_of() != SEXPTYPE::VECSXP || prot.is_altrep() || prot.xlength() < PROT_VEC_LEN {
340        return None;
341    }
342    let sym = prot.vector_elt(PROT_TYPE_ID_INDEX);
343    (sym.type_of() == SEXPTYPE::SYMSXP).then_some(sym)
344}
345
346/// `T`'s type ID as text (`TYPE_ID_CSTR` without its NUL).
347fn type_id_str<T: TypedExternal>() -> &'static str {
348    let id = T::TYPE_ID_CSTR
349        .strip_suffix(b"\0")
350        .unwrap_or(T::TYPE_ID_CSTR);
351    std::str::from_utf8(id).expect("TYPE_ID_CSTR is not valid UTF-8")
352}
353
354/// Splits a type ID of the derive's form, `crate@version::module::Type`,
355/// into `(crate, version, module::Type)`; a hand-written ID without a
356/// version is `(None, None, id)`.
357fn split_type_id(id: &str) -> (Option<&str>, Option<&str>, &str) {
358    let Some((crate_name, rest)) = id.split_once('@') else {
359        return (None, None, id);
360    };
361    match rest.split_once("::") {
362        Some((version, path)) => (Some(crate_name), Some(version), path),
363        None => (None, None, id),
364    }
365}
366
367// region: A pointer without an address
368
369/// The condition class every error about a pointer restored from a saved
370/// session carries, after its specific class.
371pub const RESTORED_CLASS: &str = "miniextendr_restored";
372
373/// The class of the error for a pointer this version of the crate saved: it
374/// came back from `readRDS()` / `unserialize()` without its Rust value.
375pub const RESTORED_NO_VALUE_CLASS: &str = "miniextendr_restored_no_value";
376
377/// The class of the error for a pointer another version of the crate saved;
378/// the condition carries `saved_version` and `current_version`.
379pub const RESTORED_OTHER_VERSION_CLASS: &str = "miniextendr_restored_other_version";
380
381/// The class of the error a rebuild hook gets when it reads a `Sidecar`
382/// field the save has no slot for: a field added after the version that
383/// saved the object (#2187). The condition carries `field`, the field's
384/// name. The `try_<field>` getters return `None` instead.
385pub const RESTORED_MISSING_SLOT_CLASS: &str = "miniextendr_restored_missing_slot";
386
387/// The class of the error for a receiver of a type's R accessors that carries
388/// no external pointer: neither a pointer nor an object holding one in
389/// `.ptr`, such as an object of the class an earlier release of a package
390/// wrote to disk (#1901). The type's restored and field-error classes go in
391/// front of it.
392pub const NO_HANDLE_CLASS: &str = "miniextendr_no_handle";
393
394/// What a pointer without an address is, as far as `T` is concerned
395/// ([`classify_null_address`]).
396#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NullAddress {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Foreign => ::core::fmt::Formatter::write_str(f, "Foreign"),
            Self::Restored =>
                ::core::fmt::Formatter::write_str(f, "Restored"),
            Self::OtherVersion {
                crate_name: __self_0, saved: __self_1, current: __self_2 } =>
                ::core::fmt::Formatter::debug_struct_field3_finish(f,
                    "OtherVersion", "crate_name", __self_0, "saved", __self_1,
                    "current", &__self_2),
            Self::OtherType { found: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "OtherType", "found", &__self_0),
        }
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for NullAddress { }
#[automatically_derived]
impl ::core::clone::Clone for NullAddress {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NullAddress { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for NullAddress { }
#[automatically_derived]
impl ::core::cmp::PartialEq for NullAddress {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::OtherVersion {
                    crate_name: __self_0, saved: __self_1, current: __self_2 },
                    Self::OtherVersion {
                    crate_name: __arg1_0, saved: __arg1_1, current: __arg1_2 })
                    =>
                    __self_0 == __arg1_0 && __self_1 == __arg1_1 &&
                        __self_2 == __arg1_2,
                (Self::OtherType { found: __self_0 }, Self::OtherType {
                    found: __arg1_0 }) => __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NullAddress {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<&'static str>;
        let _: ::core::cmp::AssertParamIsEq<&'static str>;
        let _: ::core::cmp::AssertParamIsEq<&'static str>;
        let _: ::core::cmp::AssertParamIsEq<&'static str>;
    }
}Eq)]
397pub(crate) enum NullAddress {
398    /// Not built by `ExternalPtr`: R's `new("externalptr")`, a pointer
399    /// `into_inner` cleared, another library's pointer.
400    Foreign,
401    /// Built for `T` by this version of its crate: restored from a saved
402    /// session, so the Rust value is gone.
403    Restored,
404    /// Built for `T` by another version of its crate.
405    OtherVersion {
406        /// The crate's name.
407        crate_name: &'static str,
408        /// The version that saved the pointer.
409        saved: &'static str,
410        /// This version.
411        current: &'static str,
412    },
413    /// Built for another type.
414    OtherType {
415        /// The stored type ID.
416        found: &'static str,
417    },
418}
419
420/// Reads `prot[0]` of a pointer without an address and says what it is for
421/// `T`. Symbol names live for the session, so the borrowed parts are
422/// `'static`.
423///
424/// # Safety
425///
426/// Must be called from R's main thread with a valid `EXTPTRSXP`.
427pub(crate) unsafe fn classify_null_address<T: TypedExternal>(sexp: SEXP) -> NullAddress {
428    let Some(sym) = (unsafe { stored_type_id_symbol(sexp) }) else {
429        return NullAddress::Foreign;
430    };
431    let found = symbol_name(sym);
432    let own = type_id_str::<T>();
433    if found == own {
434        return NullAddress::Restored;
435    }
436    match (split_type_id(found), split_type_id(own)) {
437        ((Some(crate_name), Some(saved), path), (Some(own_crate), Some(current), own_path))
438            if crate_name == own_crate && path == own_path =>
439        {
440            NullAddress::OtherVersion {
441                crate_name,
442                saved,
443                current,
444            }
445        }
446        _ => NullAddress::OtherType { found },
447    }
448}
449
450/// The first R class of `receiver`, which is what the user sees and the
451/// field methods dispatched on (#1920); `None` for an object without a class
452/// attribute, such as an R6 object's `.ptr`, whose messages name the Rust
453/// type instead.
454pub(crate) fn receiver_class(receiver: SEXP) -> Option<String> {
455    let class = receiver.get_class();
456    (class.type_of() == SEXPTYPE::STRSXP && class.len() > 0)
457        .then(|| class.string_elt_str(0).map(str::to_owned))
458        .flatten()
459}
460
461/// The message of the [`RESTORED_NO_VALUE_CLASS`] error.
462pub(crate) fn restored_no_value_message(type_name: &str) -> String {
463    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this `{0}` object was restored from a saved session and has no Rust value; re-create it",
                type_name))
    })format!(
464        "this `{type_name}` object was restored from a saved session and has no Rust value; \
465         re-create it"
466    )
467}
468
469/// The message of the [`RESTORED_OTHER_VERSION_CLASS`] error.
470pub(crate) fn restored_other_version_message(
471    type_name: &str,
472    crate_name: &str,
473    saved: &str,
474    current: &str,
475) -> String {
476    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this `{0}` object was saved by {1} {2} and can\'t be read by {1} {3}; re-create it",
                type_name, crate_name, saved, current))
    })format!(
477        "this `{type_name}` object was saved by {crate_name} {saved} and can't be read by \
478         {crate_name} {current}; re-create it"
479    )
480}
481
482/// The condition classes of a restored refusal of a `T`: the package's
483/// classes ([`TypedExternal::RESTORED_ERROR_CLASS`], in order), then the
484/// specific class, then [`RESTORED_CLASS`].
485pub(crate) fn restored_classes_of<T: TypedExternal>(specific: &'static str) -> Vec<String> {
486    T::RESTORED_ERROR_CLASS
487        .iter()
488        .copied()
489        .chain([specific, RESTORED_CLASS])
490        .map(str::to_owned)
491        .collect()
492}
493
494/// The message of a restored refusal of a `T`: the package's
495/// ([`TypedExternal::RESTORED_ERROR_MESSAGE`]) when it has one, else
496/// miniextendr's (`default`).
497pub(crate) fn restored_message_of<T: TypedExternal>(default: impl FnOnce() -> String) -> String {
498    T::RESTORED_ERROR_MESSAGE.map_or_else(default, str::to_owned)
499}
500
501/// Raises the error for a pointer without an address when it was built for
502/// `T` (by this version: [`RESTORED_NO_VALUE_CLASS`]; by another:
503/// [`RESTORED_OTHER_VERSION_CLASS`]), with the package's classes in front and
504/// its message when the type declares them (`#[externalptr(restored(...))]`),
505/// and returns the other cases for the caller to word as it did before. The
506/// default message names `receiver`'s R class, the object the user called
507/// on (the pointer itself, or the list, environment or S4 object carrying
508/// it), else the Rust type. The condition is raised through the panic
509/// transport (`panic_any`), like `error!`.
510///
511/// # Safety
512///
513/// Must be called from R's main thread with a valid `EXTPTRSXP` whose
514/// address is NULL.
515#[cold]
516pub(crate) unsafe fn refuse_restored<T: TypedExternal>(sexp: SEXP, receiver: SEXP) -> NullAddress {
517    let kind = unsafe { classify_null_address::<T>(sexp) };
518    let name = || receiver_class(receiver).unwrap_or_else(|| T::TYPE_NAME.to_owned());
519    let (message, class, data) = match kind {
520        NullAddress::Restored => (
521            restored_message_of::<T>(|| restored_no_value_message(&name())),
522            RESTORED_NO_VALUE_CLASS,
523            None,
524        ),
525        NullAddress::OtherVersion {
526            crate_name,
527            saved,
528            current,
529        } => (
530            restored_message_of::<T>(|| {
531                restored_other_version_message(&name(), crate_name, saved, current)
532            }),
533            RESTORED_OTHER_VERSION_CLASS,
534            Some(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(String::from("saved_version"), RValue::from(saved)),
                (String::from("current_version"), RValue::from(current))]))vec![
535                (String::from("saved_version"), RValue::from(saved)),
536                (String::from("current_version"), RValue::from(current)),
537            ]),
538        ),
539        NullAddress::Foreign | NullAddress::OtherType { .. } => return kind,
540    };
541    std::panic::panic_any(RCondition::Error {
542        message,
543        class: restored_classes_of::<T>(class),
544        data,
545        call: ConditionCall::Inherit,
546    })
547}
548
549/// The message of a refused rebuild: the restored refusal's, then the cause's.
550fn rebuild_failed_message<T: TypedExternal>(type_name: &str, cause: &str) -> String {
551    match T::RESTORED_ERROR_MESSAGE {
552        Some(message) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} (rebuilding it failed: {1})",
                message, cause))
    })format!("{message} (rebuilding it failed: {cause})"),
553        None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this `{0}` object was restored from a saved session and rebuilding it failed: {1}",
                type_name, cause))
    })format!(
554            "this `{type_name}` object was restored from a saved session and rebuilding it \
555             failed: {cause}"
556        ),
557    }
558}
559
560#[doc =
r" The pointers whose rebuild hook is running on this thread: a hook that"]
#[doc = r" reaches its own object again finds it refused, not rebuilt twice."]
const REBUILDING: ::std::thread::LocalKey<RefCell<Vec<SEXP>>> =
    {
        const __RUST_STD_INTERNAL_INIT: RefCell<Vec<SEXP>> =
            { RefCell::new(Vec::new()) };
        unsafe {
            ::std::thread::LocalKey::new(const {
                        if ::std::mem::needs_drop::<RefCell<Vec<SEXP>>>() {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL:
                                        ::std::thread::local_impl::EagerStorage<RefCell<Vec<SEXP>>>
                                        =
                                        ::std::thread::local_impl::EagerStorage::new(__RUST_STD_INTERNAL_INIT);
                                    __RUST_STD_INTERNAL_VAL.get()
                                }
                        } else {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL: RefCell<Vec<SEXP>> =
                                        __RUST_STD_INTERNAL_INIT;
                                    &__RUST_STD_INTERNAL_VAL
                                }
                        }
                    })
        }
    };std::thread_local! {
561    /// The pointers whose rebuild hook is running on this thread: a hook that
562    /// reaches its own object again finds it refused, not rebuilt twice.
563    static REBUILDING: RefCell<Vec<SEXP>> = const { RefCell::new(Vec::new()) };
564}
565
566/// Rebuilds the value of `sexp`, a pointer without an address, when `T`
567/// has a rebuild hook (`#[externalptr(rebuild = path)]`) and this version of
568/// its crate saved it, or another version did and `T` opts in with
569/// `any_version` (#2187) and the save carries slot names. Installs the value
570/// in `sexp`. Returns whether `sexp` holds a value now; `false` leaves the
571/// caller to refuse the pointer as before (another version saved it, another
572/// type, no hook, or the hook is already running for `sexp`).
573///
574/// The hook reads the saved `Sidecar` slots through [`Saved`], by field
575/// name. The installed pointer keeps each saved slot whose field `T` still
576/// has, unless the hook's struct replaces it ([`Sidecar::replace`]), and
577/// takes the value the hook's struct holds for a field the save lacks
578/// ([`install_rebuilt`]). A failing hook (an `Err`, or a condition it raises) installs
579/// nothing and raises the restored refusal of `T` (its classes, then
580/// [`RESTORED_NO_VALUE_CLASS`] and [`RESTORED_CLASS`]), the cause as
581/// `e$parent`; the next use tries again. Any other unwind (an R error, a
582/// plain panic) carries on.
583///
584/// # Safety
585///
586/// On R's main thread, with `sexp` a valid `EXTPTRSXP` whose address is NULL,
587/// rooted for the call; `receiver` is the object the user called on.
588#[cold]
589pub(crate) unsafe fn rebuild_restored<T: TypedExternal>(sexp: SEXP, receiver: SEXP) -> bool {
590    if !T::REBUILDS {
591        return false;
592    }
593    match unsafe { classify_null_address::<T>(sexp) } {
594        NullAddress::Restored => {}
595        NullAddress::OtherVersion { .. }
596            if T::REBUILDS_ANY_VERSION
597                && sidecar::has_slot_names(unsafe { R_ExternalPtrProtected(sexp) }) => {}
598        _ => return false,
599    }
600    if REBUILDING.with_borrow(|running| running.contains(&sexp)) {
601        return false;
602    }
603    struct Running(SEXP);
604    impl Drop for Running {
605        fn drop(&mut self) {
606            REBUILDING.with_borrow_mut(|running| running.retain(|&s| s != self.0));
607        }
608    }
609    REBUILDING.with_borrow_mut(|running| running.push(sexp));
610    let running = Running(sexp);
611    // SAFETY: `sexp` is a pointer this version built for a `T` (classified
612    // above), rooted by the caller for the hook's duration.
613    let saved = unsafe { Saved::<T>::new(sexp) };
614    let outcome = crate::condition::catch_condition(|| T::__mx_rebuild(&saved));
615    drop(running);
616    match outcome {
617        Ok(Ok(value)) => {
618            // SAFETY: on R's main thread; `sexp` still has no address (the
619            // hook could not install one: a reentrant use was refused).
620            unsafe { install_rebuilt(sexp, value) };
621            true
622        }
623        Ok(Err(cause)) | Err(cause) => {
624            let name = receiver_class(receiver).unwrap_or_else(|| T::TYPE_NAME.to_owned());
625            RCondition::Error {
626                message: rebuild_failed_message::<T>(&name, cause.message()),
627                class: restored_classes_of::<T>(RESTORED_NO_VALUE_CLASS),
628                data: None,
629                call: ConditionCall::Inherit,
630            }
631            .with_parent(cause)
632            .raise()
633        }
634    }
635}
636
637/// Puts the rebuilt `value` in `sexp`, a restored pointer for a `T`, as
638/// construction puts a new value in a new pointer: the finalizer first
639/// (R does not save it), then the address.
640///
641/// The pointer gets a protection list in this version's layout (#2187): its
642/// type ID and slot names, the saved user slot, and for each `Sidecar` field
643/// the saved slot of that name. `value`'s own pending value for the field
644/// is written instead when the save has no such slot, or when the hook made
645/// it with [`Sidecar::replace`]. `value`'s other pending values are dropped.
646///
647/// # Safety
648///
649/// On R's main thread, with `sexp` a valid `EXTPTRSXP` for a `T` whose
650/// address is NULL, rooted for the call.
651unsafe fn install_rebuilt<T: TypedExternal>(sexp: SEXP, value: T) {
652    if T::R_SLOT_COUNT > 0 {
653        unsafe { relayout_saved_slots(sexp, &value) };
654    }
655    discard_pending_sidecars(&value);
656    let (_, any_raw) = ExternalPtr::<T>::erase(value);
657    // Own the allocation until the finalizer that frees it is registered.
658    let allocation = unsafe { Box::from_raw(any_raw) };
659    unsafe { R_RegisterCFinalizerEx(sexp, Some(release_any), Rboolean::TRUE) };
660    let any_raw = Box::into_raw(allocation);
661    unsafe { R_SetExternalPtrAddr(sexp, any_raw.cast()) };
662}
663
664/// Replaces the saved protection list of `sexp` with one in `T`'s layout:
665/// see [`install_rebuilt`]. `value`'s pending values for the fields the save
666/// lacks are written; the rest stay pending, for the caller to drop.
667///
668/// # Safety
669///
670/// As [`install_rebuilt`].
671unsafe fn relayout_saved_slots<T: TypedExternal>(sexp: SEXP, value: &T) {
672    let saved = unsafe { R_ExternalPtrProtected(sexp) };
673    let scope = unsafe { ProtectScope::new() };
674    let prot = unsafe { Rf_allocVector(SEXPTYPE::VECSXP, prot_vec_len::<T>()) };
675    unsafe { scope.protect(prot) };
676    prot.set_vector_elt(PROT_TYPE_ID_INDEX, unsafe { type_id_symbol::<T>() });
677    prot.set_vector_elt(PROT_USER_INDEX, saved.vector_elt(PROT_USER_INDEX));
678    unsafe { sidecar::name_slots::<T>(prot) };
679    // Detached from `sexp`, the saved list stays rooted by the scope while
680    // its slots are read. The pending values the fields write are rooted
681    // until they sit in a slot (#2216).
682    unsafe { scope.protect(saved) };
683    unsafe { R_SetExternalPtrProtected(sexp, prot) };
684    value.__mx_visit_sidecars(&mut |index, name, field| match sidecar::saved_slot(
685        saved, index, name,
686    ) {
687        Some(at) if !field.__mx_replaces_saved() => {
688            prot.set_vector_elt(sidecar::prot_index(index), saved.vector_elt(at));
689        }
690        _ => field.__mx_flush(prot, index),
691    });
692}
693
694/// Empties the pointer a value was moved out of (`into_raw` / `into_inner`):
695/// the address, so the finalizer is a no-op, and the tag and protection list
696/// too, so the pointer reads as empty (the plain `null external pointer`
697/// messages) rather than as one restored from a saved session, which keeps
698/// its `prot` (#1872). `R_ClearExternalPtr` only nulls the address.
699///
700/// # Safety
701///
702/// Must be called from R's main thread with a valid `EXTPTRSXP`.
703unsafe fn clear_moved_out(sexp: SEXP) {
704    unsafe { R_ClearExternalPtr(sexp) };
705    unsafe { R_SetExternalPtrTag(sexp, SEXP::nil()) };
706    unsafe { R_SetExternalPtrProtected(sexp, SEXP::nil()) };
707}
708
709/// Test support: make a restored pointer read as a save by another version of
710/// its crate, so a package can test its handling of
711/// `miniextendr_restored_other_version` (#1872) without building two
712/// versions.
713///
714/// It rewrites the version in the type ID a restored pointer keeps in
715/// `prot[0]` (`crate@version::module::Type`) to `version`. The pointer must
716/// have no address, as one restored by `readRDS()` / `unserialize()` has: a
717/// live pointer is checked by `Any::downcast`, not by the ID. Call it from a
718/// test-only `#[miniextendr]` function:
719///
720/// ```ignore
721/// /// @param x A pointer restored by `unserialize()`.
722/// #[miniextendr(noexport)]
723/// pub fn rewrite_saved_version(x: SEXP, version: String) {
724///     // SAFETY: R's main thread; `x` is an external pointer.
725///     unsafe { miniextendr_api::externalptr::rewrite_stored_version_for_tests(x, &version) };
726/// }
727/// ```
728///
729/// ```r
730/// back <- unserialize(serialize(obj, NULL))
731/// rewrite_saved_version(back, "0.0.1")
732/// tryCatch(method(back), miniextendr_restored_other_version = function(e) e$saved_version)
733/// #> [1] "0.0.1"
734/// ```
735///
736/// # Panics
737///
738/// When `sexp` is not a restored `ExternalPtr`-built pointer with a
739/// versioned type ID.
740///
741/// # Safety
742///
743/// Must be called from R's main thread with a valid `EXTPTRSXP`.
744pub unsafe fn rewrite_stored_version_for_tests(sexp: SEXP, version: &str) {
745    if !unsafe { R_ExternalPtrAddr(sexp) }.is_null() {
    {
        ::core::panicking::panic_fmt(format_args!("the pointer still has its Rust value: save and restore it first"));
    }
};assert!(
746        unsafe { R_ExternalPtrAddr(sexp) }.is_null(),
747        "the pointer still has its Rust value: save and restore it first"
748    );
749    let sym = unsafe { stored_type_id_symbol(sexp) }.expect("an `ExternalPtr`-built pointer");
750    let (crate_name, path) = match split_type_id(symbol_name(sym)) {
751        (Some(crate_name), Some(_), path) => (crate_name, path),
752        _ => {
    ::core::panicking::panic_fmt(format_args!("the stored type ID has no version"));
}panic!("the stored type ID has no version"),
753    };
754    let rewritten = std::ffi::CString::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}@{1}::{2}", crate_name, version,
                path))
    })format!("{crate_name}@{version}::{path}"))
755        .expect("a type ID has no NUL");
756    // `Rf_install` interns the symbol for the session; the list keeps it.
757    let prot = unsafe { R_ExternalPtrProtected(sexp) };
758    prot.set_vector_elt(PROT_TYPE_ID_INDEX, unsafe {
759        Rf_install(rewritten.as_ptr())
760    });
761}
762
763/// Lays the `Sidecar` slots of a restored pointer's save out as another
764/// version of its type might have saved them, for tests of reading a save by
765/// field name (#2187). Slot `i` of the new list holds the slot named
766/// `layout[i]` in the current save, or the string `"unknown"` for a name the
767/// save has no slot for. The list carries the names `layout` unless `named`
768/// is false, which gives a save made before slots were named.
769///
770/// # Panics
771///
772/// When `sexp` still has its Rust value, or its save carries no slot names.
773///
774/// # Safety
775///
776/// Must be called from R's main thread with a valid `EXTPTRSXP`.
777#[doc(hidden)]
778pub unsafe fn rewrite_saved_slots_for_tests(sexp: SEXP, layout: &[&str], named: bool) {
779    if !unsafe { R_ExternalPtrAddr(sexp) }.is_null() {
    {
        ::core::panicking::panic_fmt(format_args!("the pointer still has its Rust value: save and restore it first"));
    }
};assert!(
780        unsafe { R_ExternalPtrAddr(sexp) }.is_null(),
781        "the pointer still has its Rust value: save and restore it first"
782    );
783    let saved = unsafe { R_ExternalPtrProtected(sexp) };
784    if !sidecar::has_slot_names(saved) {
    {
        ::core::panicking::panic_fmt(format_args!("the save carries no slot names"));
    }
};assert!(
785        sidecar::has_slot_names(saved),
786        "the save carries no slot names"
787    );
788    let names = saved.get_attr(SEXP::names_symbol());
789    let len = PROT_VEC_LEN
790        + R_xlen_t::try_from(layout.len()).expect("the layout length fits in R_xlen_t");
791    let scope = unsafe { ProtectScope::new() };
792    let prot = unsafe { Rf_allocVector(SEXPTYPE::VECSXP, len) };
793    unsafe { scope.protect(prot) };
794    prot.set_vector_elt(PROT_TYPE_ID_INDEX, saved.vector_elt(PROT_TYPE_ID_INDEX));
795    prot.set_vector_elt(PROT_USER_INDEX, saved.vector_elt(PROT_USER_INDEX));
796    for (index, name) in layout.iter().enumerate() {
797        let found =
798            (PROT_VEC_LEN..names.xlength()).find(|&at| names.string_elt_str(at) == Some(name));
799        let slot = match found {
800            Some(at) => saved.vector_elt(at),
801            None => crate::into_r::IntoR::into_sexp("unknown"),
802        };
803        prot.set_vector_elt(sidecar::prot_index(index), slot);
804    }
805    if named {
806        let new_names = unsafe { Rf_allocVector(SEXPTYPE::STRSXP, len) };
807        unsafe { scope.protect(new_names) };
808        for (index, name) in layout.iter().enumerate() {
809            new_names.set_string_elt(sidecar::prot_index(index), SEXP::charsxp(name));
810        }
811        prot.set_names(new_names);
812    }
813    unsafe { R_SetExternalPtrProtected(sexp, prot) };
814}
815
816// endregion
817
818// region: TypedExternalPtr Trait
819
820/// Trait for types that can be stored in an ExternalPtr.
821///
822/// This provides R-visible display and diagnostic identifiers. Runtime type
823/// checking is performed by `Any::downcast` (Rust's `TypeId`), not by comparing
824/// these symbols.
825///
826/// # Type ID vs Type Name
827///
828/// - `TYPE_ID_CSTR`: Namespaced identifier used in mismatch diagnostics (stored in `prot[0]`).
829///   Format: `"<crate_name>@<crate_version>::<module_path>::<type_name>\0"`
830///
831///   The crate name, version, and module path distinguish otherwise similar
832///   names in error messages. They do not determine compatibility.
833///
834/// - `TYPE_NAME_CSTR`: Short display name for the R tag (shown when printing).
835///   Just the type identifier for readability.
836#[diagnostic::on_unimplemented(
837    message = "`{Self}` is not a type an `ExternalPtr` stores",
838    label = "`{Self}` does not implement `TypedExternal`",
839    note = "`#[derive(ExternalPtr)]` (or `impl_typed_external!`) implements it; R reaches a `{Self}` through an external pointer, as the object of a `#[miniextendr]` impl or a `&{Self}` / `&mut {Self}` parameter"
840)]
841pub trait TypedExternal: 'static {
842    /// The type name as a static string (for debugging and display)
843    const TYPE_NAME: &'static str;
844
845    /// The type name as a null-terminated C string (for R tag display)
846    const TYPE_NAME_CSTR: &'static [u8];
847
848    /// Namespaced type ID as a null-terminated C string (for diagnostics).
849    ///
850    /// This should include the module path to prevent ambiguous messages.
851    /// Use `concat!(module_path!(), "::", stringify!(Type), "\0").as_bytes()`
852    /// when implementing manually, or use `#[derive(ExternalPtr)]`.
853    const TYPE_ID_CSTR: &'static [u8];
854
855    /// Number of [`Sidecar<T>`] fields: values the external pointer's
856    /// protection list keeps after the type ID and the user slot.
857    ///
858    /// `#[derive(ExternalPtr)]` counts the type's `#[r_data]` fields of type
859    /// `Sidecar<_>`.
860    const R_SLOT_COUNT: usize = 0;
861
862    /// The R names of the `Sidecar` fields (`#[r_data(name = "..")]`, else
863    /// the Rust field name), in slot order: the names the protection list
864    /// carries, so a save is read back by field name (#2187, #2215).
865    #[doc(hidden)]
866    const SIDECAR_NAMES: &'static [&'static str] = &[];
867
868    /// The package's condition classes for a pointer of this type restored
869    /// from a saved session, in front of the `miniextendr_restored*` classes
870    /// ([`RESTORED_NO_VALUE_CLASS`] / [`RESTORED_OTHER_VERSION_CLASS`], then
871    /// [`RESTORED_CLASS`]), which every restored refusal keeps.
872    ///
873    /// `#[derive(ExternalPtr)]` fills it from
874    /// `#[externalptr(restored(class = "pkg_saved"))]` or
875    /// `restored(class = ["pkg_saved", "pkg_error"])`; the default adds
876    /// nothing.
877    const RESTORED_ERROR_CLASS: &'static [&'static str] = &[];
878
879    /// The package's message for a pointer of this type restored from a saved
880    /// session, replacing miniextendr's in both the no-value and the
881    /// other-version case (the condition's `saved_version` /
882    /// `current_version` fields stay), and for a list or environment of the
883    /// class that carries no pointer (a save in an older format). A static
884    /// string: one type is one R class, so the package names it literally.
885    ///
886    /// `#[derive(ExternalPtr)]` fills it from
887    /// `#[externalptr(restored(message = "..."))]`; the default keeps
888    /// miniextendr's messages.
889    const RESTORED_ERROR_MESSAGE: Option<&'static str> = None;
890
891    /// The package's condition classes for the errors the type's R field
892    /// readers raise themselves (#1899), in front of their own class: a
893    /// refused position (`subscriptOutOfBoundsError`), a name that is not a
894    /// field (`miniextendr_no_field`), a computed field assigned to
895    /// (`miniextendr_read_only_field`), a pointer of another type, and a
896    /// receiver that carries no pointer ([`NO_HANDLE_CLASS`]). The R
897    /// accessors of the type read it for the last two; the derive writes the
898    /// same classes into the R helpers for the others.
899    ///
900    /// `#[derive(ExternalPtr)]` fills it from
901    /// `#[externalptr(field_error(class = ["pkg_error_config", "pkg_error"]))]`;
902    /// the default adds nothing.
903    const FIELD_ERROR_CLASS: &'static [&'static str] = &[];
904
905    /// The package's message for the R accessors' refusal of a receiver
906    /// whose pointer is not a `T` or that carries no pointer (#1920),
907    /// replacing miniextendr's, which names the receiver's R class.
908    ///
909    /// `#[derive(ExternalPtr)]` fills it from
910    /// `#[externalptr(field_error(message = "..."))]`.
911    const FIELD_ERROR_MESSAGE: Option<&'static str> = None;
912
913    /// Visits every [`Sidecar<T>`] field of `self` with its slot index and
914    /// name, so the handle can flush the field's pending value into its slot
915    /// or load the slot back into the field.
916    ///
917    /// `#[derive(ExternalPtr)]` emits the body for a type with `Sidecar`
918    /// fields; the default visits nothing, so other types pay nothing.
919    #[doc(hidden)]
920    #[inline]
921    fn __mx_visit_sidecars(&self, _visit: &mut dyn FnMut(usize, &'static str, &dyn SidecarField)) {}
922
923    /// Whether a pointer of this type that this version of its crate saved
924    /// is rebuilt on first use after `readRDS()`, by [`Self::__mx_rebuild`],
925    /// instead of refused.
926    ///
927    /// `#[derive(ExternalPtr)]` sets it with `#[externalptr(rebuild = path)]`.
928    #[doc(hidden)]
929    const REBUILDS: bool = false;
930
931    /// Whether [`Self::__mx_rebuild`] also rebuilds a pointer another
932    /// version of the crate saved, when the save carries slot names.
933    /// `#[derive(ExternalPtr)]` sets it with `any_version` next to
934    /// `rebuild = path` (#2187).
935    #[doc(hidden)]
936    const REBUILDS_ANY_VERSION: bool = false;
937
938    /// Rebuilds the value of a pointer of this type restored from a saved
939    /// session, from what the save kept. `#[derive(ExternalPtr)]` emits it for
940    /// `#[externalptr(rebuild = path)]`: it calls `path` and turns its `Err`
941    /// into a condition.
942    #[doc(hidden)]
943    fn __mx_rebuild(saved: &Saved<'_, Self>) -> Result<Self, RCondition>
944    where
945        Self: Sized,
946    {
947        let _ = saved;
948        {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("`{0}` has no rebuild hook", Self::TYPE_NAME)));
}unreachable!("`{}` has no rebuild hook", Self::TYPE_NAME)
949    }
950}
951
952/// Marker trait for types that should be converted to R as ExternalPtr.
953///
954/// When a type implements this trait (via `#[derive(ExternalPtr)]`), it gets a
955/// blanket `IntoR` implementation that wraps the value in `ExternalPtr<T>`.
956///
957/// This allows returning the type directly from `#[miniextendr]` functions:
958///
959/// ```ignore
960/// #[derive(ExternalPtr)]
961/// struct MyData { value: i32 }
962///
963/// #[miniextendr]
964/// fn create_data(v: i32) -> MyData {
965///     MyData { value: v }  // Automatically wrapped in ExternalPtr
966/// }
967/// ```
968pub trait IntoExternalPtr: TypedExternal {}
969
970impl TypedExternal for () {
971    const TYPE_NAME: &'static str = "()";
972    const TYPE_NAME_CSTR: &'static [u8] = b"()\0";
973    // Unit type is special - same ID as name since it's only used for type-erased ptrs
974    const TYPE_ID_CSTR: &'static [u8] = b"()\0";
975}
976// endregion
977
978// region: Class-handle unwrapping (audit A9)
979
980/// Look up a variable bound directly in a single environment frame (no search
981/// of enclosing frames) with [`REnv::get`](crate::expression::REnv::get).
982///
983/// Returns `None` for a non-environment, an absent binding, or `NULL` (none
984/// can hold a class handle). Promises and active bindings are evaluated once;
985/// their errors unwind to the caller's miniextendr boundary.
986///
987/// # Safety
988///
989/// R's main thread, inside a miniextendr boundary.
990unsafe fn env_binding(env: SEXP, name: &str) -> Option<SEXP> {
991    unsafe {
992        if !env.is_environment() {
993            return None;
994        }
995        crate::expression::REnv::from_sexp(env)
996            .get(name)
997            .filter(|val| !val.is_null_or_nil())
998    }
999}
1000
1001/// Attempt to unwrap a class-wrapped handle down to the bare `EXTPTRSXP` it
1002/// carries, so [`ExternalPtr::<T>`](ExternalPtr) argument conversion accepts
1003/// the ergonomic class handle (e.g. `Foo$new(...)`) in addition to the raw
1004/// pointer returned by a low-level constructor (audit finding A9 —
1005/// `audit/2026-07-03-api-sense-conversions-dataframe-errors.md` #5).
1006///
1007/// Tries, in order:
1008/// - **Env / R6**: a direct `.ptr` binding on `sexp` itself — most
1009///   `#[miniextendr(env)]` classes are actually a bare classed `EXTPTRSXP`
1010///   (the generated constructor does `class(.val) <- "T"` directly on the
1011///   pointer returned by Rust, see `env_class.rs`), which already satisfies
1012///   the plain `EXTPTRSXP` check and never reaches this function, but a
1013///   user-authored environment that binds `.ptr` is covered here too. Then
1014///   the R6 handle chain `.__enclos_env__` -> `private` -> `.ptr` (R6
1015///   objects are the *public* environment; `private` only hangs off the
1016///   enclosing environment stored at `.__enclos_env__` for `portable`
1017///   classes, the default — see `r6_class.rs`).
1018/// - **S4**: the `ptr` slot via `methods::slot()`
1019///   ([`crate::s4_helpers::s4_get_slot`]). Guarded by `isS4()`, which
1020///   excludes S7 objects even though both share the `S4SXP`/`OBJSXP`
1021///   `SEXPTYPE` — S7's `new_object(S7_object(), ...)` base never sets the S4
1022///   bit.
1023/// - **List**: a generic vector with a `.ptr` element (`x[[".ptr"]]`), the
1024///   shape of a hand-built S3 object that keeps R-side state next to the
1025///   handle: `structure(list(.ptr = <ptr>, log = ...), class = "Foo")`
1026///   (#1469). The `.ptr` spelling matches the env / R6 / S7 convention.
1027/// - **Anything else carrying a `.ptr` attribute**: S7 stores properties as
1028///   plain attributes on its base object (see `s7_class.rs`), so
1029///   `Rf_getAttrib(x, ".ptr")` recovers the pointer without going through
1030///   S7's `@`/`prop()` dispatch machinery. A list without a `.ptr` element
1031///   falls through to this check too (an S7 class over `class_list` is a
1032///   `VECSXP` with its properties as attributes).
1033///
1034/// Both the `ExternalPtr<T>` argument conversion and, via
1035/// `resolve_receiver`, every generated instance-method prelude go through
1036/// this function.
1037///
1038/// Returns `Some(inner)` only when the unwrapped value is itself an
1039/// `EXTPTRSXP` — anything else (e.g. a `.ptr`-named field that isn't a
1040/// pointer) is treated as "no handle found" rather than an error. No
1041/// recursion beyond one unwrap level. `Any::downcast` remains the type-safety
1042/// authority: unwrapping a handle for the *wrong* `T` still fails at the
1043/// caller with the existing type-mismatch error — this only loosens the
1044/// accepted R-side shape, not type safety.
1045///
1046/// # Safety
1047///
1048/// - Must be called from R's main thread, inside a miniextendr boundary: an R
1049///   error from a promise or active binding the lookup forces unwinds to it.
1050/// - The returned SEXP is reachable from `sexp` (an env binding, S4 slot, or
1051///   attribute) for as long as `sexp` itself is protected. Macro-generated
1052///   `.Call()` wrappers hold every argument alive in the call's PROTECT stack
1053///   for the duration of the call, so no additional protection is needed
1054///   here.
1055pub(crate) unsafe fn unwrap_class_handle(sexp: SEXP) -> Option<SEXP> {
1056    unsafe {
1057        if sexp.is_environment() {
1058            if let Some(direct) = env_binding(sexp, ".ptr") {
1059                if direct.type_of() == SEXPTYPE::EXTPTRSXP {
1060                    return Some(direct);
1061                }
1062            }
1063            // An active binding can return a fresh environment that is not
1064            // retained by its owner. Keep it rooted across the next lookup.
1065            let scope = ProtectScope::new();
1066            let enclos = scope.protect_raw(env_binding(sexp, ".__enclos_env__")?);
1067            let private = scope.protect_raw(env_binding(enclos, "private")?);
1068            let inner = env_binding(private, ".ptr")?;
1069            return (inner.type_of() == SEXPTYPE::EXTPTRSXP).then_some(inner);
1070        }
1071
1072        if sexp.is_s4() {
1073            let slot = crate::s4_helpers::s4_get_slot(sexp, "ptr").ok()?;
1074            return (slot.type_of() == SEXPTYPE::EXTPTRSXP).then_some(slot);
1075        }
1076
1077        if sexp.type_of() == SEXPTYPE::VECSXP {
1078            if let Some(elt) = list_element(sexp, ".ptr") {
1079                if elt.type_of() == SEXPTYPE::EXTPTRSXP {
1080                    return Some(elt);
1081                }
1082            }
1083        }
1084
1085        let attr = sexp.get_attr(Rf_install(c".ptr".as_ptr()));
1086        (attr.type_of() == SEXPTYPE::EXTPTRSXP).then_some(attr)
1087    }
1088}
1089
1090/// The element of a generic vector bound to `name`: the first match in
1091/// `names(x)`, like `x[[name]]`. `None` when `x` has no character names or
1092/// no element by that name.
1093///
1094/// # Safety
1095///
1096/// Must be called from R's main thread; `sexp` must be a `VECSXP`.
1097unsafe fn list_element(sexp: SEXP, name: &str) -> Option<SEXP> {
1098    let names = sexp.get_names();
1099    if names.type_of() != SEXPTYPE::STRSXP {
1100        return None;
1101    }
1102    let n = names.len().min(sexp.len());
1103    for i in 0..n {
1104        let i = isize::try_from(i).ok()?;
1105        if names.string_elt_str(i) == Some(name) {
1106            return Some(sexp.vector_elt(i));
1107        }
1108    }
1109    None
1110}
1111
1112/// Resolve an instance-method receiver to the bare `EXTPTRSXP` it carries.
1113///
1114/// Generated method preludes call this on `self_sexp` before
1115/// `ErasedExternalPtr::from_sexp` / `ExternalPtr::<T>::wrap_sexp`. A bare
1116/// pointer, the shape every generated constructor returns (classed or not),
1117/// passes through after one `TYPEOF` compare. Anything else goes through the
1118/// same class-handle unwrap as `ExternalPtr<T>` arguments: an R6 / S4 / S7
1119/// handle, an environment or a list carrying the pointer in `.ptr`, or any
1120/// object with a `.ptr` attribute. An S3 class whose object is a list with
1121/// R-side state next to the handle, `structure(list(.ptr = <ptr>, log = ...),
1122/// class = "Foo")`, therefore dispatches into `#[miniextendr(s3)] impl Foo`
1123/// methods unchanged (#1469).
1124///
1125/// Panics (the framework converts it to an R error) when no pointer can be
1126/// recovered, naming `T` and the receiver's `SEXPTYPE`. Type safety is still
1127/// decided by the `Any::downcast` that follows; this only widens the accepted
1128/// R-side shape.
1129///
1130/// Runs on R's main thread: generated preludes execute before any worker
1131/// hand-off.
1132#[inline]
1133pub fn resolve_receiver<T: TypedExternal>(sexp: SEXP) -> SEXP {
1134    let ptr = if sexp.type_of() == SEXPTYPE::EXTPTRSXP {
1135        sexp
1136    } else {
1137        match unsafe { unwrap_class_handle(sexp) } {
1138            Some(inner) => inner,
1139            None => receiver_not_a_handle::<T>(sexp),
1140        }
1141    };
1142    // A pointer restored from a saved session gets its value back here when
1143    // the type has a rebuild hook, before the receiver is borrowed.
1144    // SAFETY: on R's main thread; `ptr` is a valid EXTPTRSXP reachable from
1145    // `sexp`, which the call's frame roots.
1146    if T::REBUILDS && unsafe { R_ExternalPtrAddr(ptr) }.is_null() {
1147        unsafe { rebuild_restored::<T>(ptr, sexp) };
1148    }
1149    ptr
1150}
1151
1152/// The typed handle of an instance-method receiver, for `self: ExternalPtr<Self>`
1153/// receivers: [`resolve_receiver`]
1154/// then [`ExternalPtr::wrap_sexp`], failing like the erased receivers do
1155/// ([`handle_downcast_failed`]: consumed, restored from a saved session, or a
1156/// type mismatch). Runs on R's main thread (the generated prelude).
1157#[inline]
1158pub fn wrap_receiver<T: TypedExternal>(sexp: SEXP) -> ExternalPtr<T> {
1159    let ptr = resolve_receiver::<T>(sexp);
1160    // SAFETY: `ptr` is a valid EXTPTRSXP the call's frame roots; on R's main
1161    // thread. The handle borrows it for the method's duration, like the
1162    // erased receivers.
1163    match unsafe { ExternalPtr::<T>::wrap_sexp(ptr) } {
1164        Some(handle) => handle,
1165        None => {
1166            let erased = unsafe { ErasedExternalPtr::from_sexp(ptr) };
1167            handle_downcast_failed::<T>(&erased)
1168        }
1169    }
1170}
1171
1172#[cold]
1173fn receiver_not_a_handle<T: TypedExternal>(sexp: SEXP) -> ! {
1174    {
    ::core::panicking::panic_fmt(format_args!("expected a `{0}` object (an external pointer, or an R6/S4/S7 handle, environment, or list carrying one in `.ptr`), got {1:?}",
            ExternalPtr::<T>::type_name(), sexp.type_of()));
}panic!(
1175        "expected a `{}` object (an external pointer, or an R6/S4/S7 handle, \
1176         environment, or list carrying one in `.ptr`), got {:?}",
1177        ExternalPtr::<T>::type_name(),
1178        sexp.type_of()
1179    )
1180}
1181// endregion
1182
1183// region: Receiver borrows
1184
1185/// A generated method's receiver: the `EXTPTRSXP` [`resolve_receiver`] finds
1186/// in the `.Call` argument, rooted for the call when a class handle carried
1187/// it. R code the method calls back into can drop the handle's reference, and
1188/// the finalizer would then free the value under the method's borrow. A bare
1189/// pointer is the `.Call` argument itself, which R keeps alive.
1190#[doc(hidden)]
1191pub struct ReceiverOwner {
1192    sexp: SEXP,
1193    _root: Option<PoolRoot>,
1194}
1195
1196impl ReceiverOwner {
1197    /// Resolve `sexp` like [`resolve_receiver`]. Runs on R's main thread (the
1198    /// generated prelude).
1199    #[inline]
1200    pub fn resolve<T: TypedExternal>(sexp: SEXP) -> Self {
1201        let owner = resolve_receiver::<T>(sexp);
1202        // SAFETY: on R's main thread; `owner` is reachable through `sexp`,
1203        // which the call's frame roots.
1204        let root = (owner != sexp).then(|| unsafe { PoolRoot::new(owner) });
1205        Self {
1206            sexp: owner,
1207            _root: root,
1208        }
1209    }
1210
1211    /// The resolved `EXTPTRSXP`.
1212    #[inline]
1213    pub fn get(&self) -> SEXP {
1214        self.sexp
1215    }
1216
1217    /// The resolved `EXTPTRSXP`, as the owner of an [`ExternalPtrRef`] /
1218    /// [`ExternalPtrMut`] receiver.
1219    #[inline]
1220    pub fn owner(&self) -> &SEXP {
1221        &self.sexp
1222    }
1223}
1224
1225/// Borrow a stored value shared for a generated wrapper: the R error of
1226/// class `miniextendr_borrow_conflict` while a running call holds it mutably.
1227#[doc(hidden)]
1228#[inline]
1229pub fn borrow_value<T>(cell: &RefCell<T>) -> Ref<'_, T> {
1230    cell.try_borrow().unwrap_or_else(|_| borrow_conflict::<T>())
1231}
1232
1233/// Borrow a stored value exclusively for a generated wrapper: the R error of
1234/// class `miniextendr_borrow_conflict` while a running call holds it.
1235#[doc(hidden)]
1236#[inline]
1237pub fn borrow_value_mut<T>(cell: &RefCell<T>) -> RefMut<'_, T> {
1238    cell.try_borrow_mut()
1239        .unwrap_or_else(|_| borrow_conflict::<T>())
1240}
1241
1242// The generated wrapper reaches a borrowed argument's value only through
1243// these functions, each bounded on `TypedExternal` and spanned on the
1244// parameter's type: for a `T` that isn't `TypedExternal` rustc then reports
1245// the one missing bound once, with its `on_unimplemented` message.
1246
1247/// [`ExternalPtr::borrow`] for a generated wrapper's argument.
1248#[doc(hidden)]
1249#[inline]
1250pub fn __mx_borrow<T: TypedExternal>(ptr: &ExternalPtr<T>) -> ExternalPtrRef<'_, T> {
1251    ptr.borrow()
1252}
1253
1254/// [`ExternalPtr::borrow_mut`] for a generated wrapper's argument.
1255#[doc(hidden)]
1256#[inline]
1257pub fn __mx_borrow_mut<T: TypedExternal>(ptr: &mut ExternalPtr<T>) -> ExternalPtrMut<'_, T> {
1258    ptr.borrow_mut()
1259}
1260
1261/// The `&T` a generated wrapper hands a `&T` parameter.
1262#[doc(hidden)]
1263#[inline]
1264pub fn __mx_shared<'a, T: TypedExternal>(view: &'a ExternalPtrRef<'_, T>) -> &'a T {
1265    view
1266}
1267
1268/// The `&mut T` a generated wrapper hands a `&mut T` parameter.
1269#[doc(hidden)]
1270#[inline]
1271pub fn __mx_exclusive<'a, T: TypedExternal>(view: &'a mut ExternalPtrMut<'_, T>) -> &'a mut T {
1272    view
1273}
1274
1275#[cold]
1276fn borrow_conflict<T>() -> ! {
1277    {
    let (__mx_call, __mx_class, __mx_data, __mx_message) =
        {
            let (__mx_class, __mx_data, __mx_message) =
                (crate::condition::ConditionClass::into_condition_class("miniextendr_borrow_conflict"),
                    ::std::option::Option::<crate::condition::ConditionData>::None,
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("this `{0}` object is in use by a call that is still running",
                                    core::any::type_name::<T>()))
                        }));
            (crate::condition::ConditionCall::Inherit, __mx_class, __mx_data,
                __mx_message)
        };
    ::std::panic::panic_any(crate::condition::RCondition::Error {
            message: __mx_message,
            class: __mx_class,
            data: __mx_data,
            call: __mx_call,
        })
}crate::error!(
1278        class = "miniextendr_borrow_conflict",
1279        "this `{}` object is in use by a call that is still running",
1280        core::any::type_name::<T>()
1281    )
1282}
1283// endregion
1284
1285// region: ExternalPtr<T>
1286
1287/// An owned pointer stored in R's external pointer SEXP.
1288///
1289/// This is conceptually similar to `Box<T>`, but with the following differences:
1290/// - Memory is freed by R's GC via a registered finalizer (non-deterministic)
1291/// - The underlying SEXP is Copy, so aliasing must be manually prevented
1292/// - Type checking happens at runtime via `Any::downcast` (Rust `TypeId`)
1293///
1294/// # Thread Safety
1295///
1296/// `ExternalPtr` is `Send` to allow returning from worker thread functions.
1297/// However, **concurrent access is not allowed** - R's runtime is single-threaded.
1298/// All R API calls are serialized through the main thread via `with_r_thread`.
1299///
1300/// # Reading the value
1301///
1302/// A handle has no `Deref`: [`borrow`](Self::borrow) /
1303/// [`borrow_mut`](Self::borrow_mut) read the value, checking its `RefCell`
1304/// (see "Reentrant calls" in the [module docs](self)). A `#[miniextendr]`
1305/// function that needs only the value takes `&T` / `&mut T` instead, and the
1306/// generated wrapper borrows it for the call.
1307///
1308/// # Safety
1309///
1310/// The ExternalPtr assumes exclusive ownership of the underlying data.
1311/// Cloning the raw SEXP without proper handling will lead to double-free.
1312///
1313/// # Examples
1314///
1315/// ```no_run
1316/// use miniextendr_api::externalptr::{ExternalPtr, TypedExternal};
1317///
1318/// struct MyData { value: f64 }
1319/// impl TypedExternal for MyData {
1320///     const TYPE_NAME: &'static str = "MyData";
1321///     const TYPE_NAME_CSTR: &'static [u8] = b"MyData\0";
1322///     const TYPE_ID_CSTR: &'static [u8] = b"my_crate::MyData\0";
1323/// }
1324///
1325/// let ptr = ExternalPtr::new(MyData { value: 3.14 });
1326/// assert_eq!(ptr.borrow().value, 3.14);
1327/// ```
1328#[repr(C)]
1329pub struct ExternalPtr<T: TypedExternal> {
1330    sexp: SEXP,
1331    /// Cached address of the value's `RefCell`, set once at construction time.
1332    ///
1333    /// This avoids the `R_ExternalPtrAddr` FFI call on every `borrow()`/`borrow_mut()`.
1334    /// The pointer remains valid for the lifetime of the `ExternalPtr` because:
1335    /// - R's finalizer only runs after R garbage-collects the SEXP (which cannot
1336    ///   happen while a Rust `ExternalPtr` value exists).
1337    /// - `R_ClearExternalPtr` is only called in methods that consume or finalize
1338    ///   (`into_raw`, `into_inner`, `release_any`).
1339    cached_ptr: NonNull<RefCell<T>>,
1340    /// The [`ProtectPool`] key rooting this handle's `EXTPTRSXP`, or `None` for
1341    /// borrowed views.
1342    ///
1343    /// `Some(key)` for *owning* handles built from a fresh value (`new` /
1344    /// `new_unchecked` / `from_raw` / `Clone` / `Default`): the constructor
1345    /// roots the `EXTPTRSXP` in the main-thread pool so it stays alive for the
1346    /// whole Rust lifetime of the handle — including while it sits in a `Vec`
1347    /// across other R allocations before being handed to R (#836). `Drop` /
1348    /// `into_raw` / `into_inner` release the root via the key.
1349    ///
1350    /// `None` for *borrowed* views of an SEXP R already owns (`wrap_sexp*` /
1351    /// `from_sexp*` / `same_object`): no root is taken, so none is released. The
1352    /// aliased object is kept alive by whatever R-side reference handed it to us
1353    /// (a `.Call` argument frame, an owning sibling handle, …).
1354    root: Option<ProtectKey>,
1355    _marker: PhantomData<T>,
1356}
1357
1358// SAFETY: ExternalPtr can be sent between threads because:
1359// 1. All R API operations are serialized through the main thread via with_r_thread
1360// 2. The worker thread is blocked while the main thread processes R calls
1361// 3. There is no concurrent access - only sequential hand-off between threads
1362unsafe impl<T: TypedExternal + Send> Send for ExternalPtr<T> {}
1363
1364// region: ExternalPtr GC roots
1365//
1366// Owning `ExternalPtr` handles keep their `EXTPTRSXP` alive for the handle's
1367// whole Rust lifetime by rooting it in a process-wide `ProtectPool` — a single
1368// GC-traced VECSXP with Rust-side slot bookkeeping. This is what makes a naive
1369// `Vec<ExternalPtr<T>>` GC-safe: every element stays rooted while later elements
1370// allocate (#836).
1371//
1372// Why a pool and not `R_PreserveObject`: the pool releases in O(1) any order,
1373// whereas `R_ReleaseObject` scans R's precious list (O(n)). A `Vec<ExternalPtr>`
1374// drops front-to-back — oldest first, i.e. the entries deepest in R's LIFO
1375// precious list — so `R_PreserveObject` rooting degrades to O(n²) on exactly
1376// this workload (60–65× slower at 10k; see
1377// analysis/gc-protection-benchmarks-results.md). The pool is the mechanism the
1378// strategy analysis prescribes for ExternalPtr (analysis/gc-protection-strategies.md).
1379//
1380// `ProtectPool` is `!Send`/`!Sync` and lives in a `thread_local!` on R's main
1381// thread. Every access happens there: roots are taken inside
1382// `create_extptr_sexp[_unchecked]` (main-thread by contract / `with_r_thread`),
1383// and released through `with_r_thread` from `Drop` / `into_raw` / `into_inner`.
1384// The pool is wrapped in `ManuallyDrop` so it is never released at thread exit —
1385// it is a session-lifetime root table, and running `R_ReleaseObject` on its
1386// backing during R's own teardown would touch a half-freed R heap.
1387
1388const EXTPTR_ROOTS:
    ::std::thread::LocalKey<RefCell<Option<ManuallyDrop<ProtectPool>>>> =
    {
        const __RUST_STD_INTERNAL_INIT:
            RefCell<Option<ManuallyDrop<ProtectPool>>> =
            { RefCell::new(None) };
        unsafe {
            ::std::thread::LocalKey::new(const {
                        if ::std::mem::needs_drop::<RefCell<Option<ManuallyDrop<ProtectPool>>>>()
                            {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL:
                                        ::std::thread::local_impl::EagerStorage<RefCell<Option<ManuallyDrop<ProtectPool>>>>
                                        =
                                        ::std::thread::local_impl::EagerStorage::new(__RUST_STD_INTERNAL_INIT);
                                    __RUST_STD_INTERNAL_VAL.get()
                                }
                        } else {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL:
                                        RefCell<Option<ManuallyDrop<ProtectPool>>> =
                                        __RUST_STD_INTERNAL_INIT;
                                    &__RUST_STD_INTERNAL_VAL
                                }
                        }
                    })
        }
    };thread_local! {
1389    static EXTPTR_ROOTS: RefCell<Option<ManuallyDrop<ProtectPool>>> = const { RefCell::new(None) };
1390}
1391
1392/// Root an owning handle's `EXTPTRSXP` in the main-thread pool.
1393///
1394/// Must run on R's main thread, which holds inside
1395/// `create_extptr_sexp[_unchecked]` and `TraitViewData::new`, whose trait views
1396/// root their receiver here too. `sexp` may be fresh: it stays reachable while
1397/// the first call creates the pool and while the pool grows.
1398#[inline]
1399pub(crate) fn root_owned(sexp: SEXP) -> ProtectKey {
1400    EXTPTR_ROOTS.with_borrow_mut(|slot| {
1401        let pool = slot.get_or_insert_with(|| {
1402            // SAFETY: on R's main thread (caller contract). Creating the pool
1403            // allocates before it holds `sexp`.
1404            let _sexp = unsafe { crate::OwnedProtect::new(sexp) };
1405            ManuallyDrop::new(ProtectPool::new(ProtectPool::DEFAULT_CAPACITY))
1406        });
1407        pool.insert(sexp)
1408    })
1409}
1410
1411/// Release an owning handle's pool root. Stale keys are a safe no-op.
1412///
1413/// Must run on R's main thread (callers route through `with_r_thread`, or are
1414/// `!Send` like `TraitViewData`).
1415#[inline]
1416pub(crate) fn unroot_owned(key: ProtectKey) {
1417    EXTPTR_ROOTS.with_borrow_mut(|slot| {
1418        if let Some(pool) = slot.as_mut() {
1419            pool.release(key);
1420        }
1421    });
1422}
1423
1424/// One R object rooted in the main-thread pool, released when dropped.
1425///
1426/// For roots that R's finalizers drop, such as the caches an ALTREP owner
1427/// keeps in data1. GC finalizes the newest objects first, while
1428/// `R_ReleaseObject` scans the precious list from its head, so precious-list
1429/// roots taken in any other order release in quadratic time. The pool
1430/// releases in O(1) whatever the order.
1431///
1432/// Raw-pointer marker: `!Send` and `!Sync`, so it is dropped on the thread
1433/// that took it.
1434pub(crate) struct PoolRoot {
1435    sexp: SEXP,
1436    key: ProtectKey,
1437    _not_send: PhantomData<*mut ()>,
1438}
1439
1440impl PoolRoot {
1441    /// Root `sexp`, which needs no protection of its own.
1442    ///
1443    /// # Safety
1444    ///
1445    /// Must run on R's main thread.
1446    pub(crate) unsafe fn new(sexp: SEXP) -> Self {
1447        Self {
1448            sexp,
1449            key: root_owned(sexp),
1450            _not_send: PhantomData,
1451        }
1452    }
1453
1454    /// The rooted object.
1455    pub(crate) fn get(&self) -> SEXP {
1456        self.sexp
1457    }
1458}
1459
1460impl Drop for PoolRoot {
1461    fn drop(&mut self) {
1462        // Releasing a slot neither allocates nor runs finalizers, so this is
1463        // sound inside a finalizer too.
1464        unroot_owned(self.key);
1465    }
1466}
1467// endregion
1468
1469impl<T: TypedExternal> ExternalPtr<T> {
1470    /// Build an *owning* handle rooted at `root`.
1471    ///
1472    /// Pairs with the [`ProtectPool`] root that [`create_extptr_sexp`] /
1473    /// [`create_extptr_sexp_unchecked`] take on the SEXP (and return as the
1474    /// key). The root is released by `Drop` / `into_raw` / `into_inner`. Only
1475    /// the four fresh-value constructors (`new` / `new_unchecked` / `from_raw` /
1476    /// `from_raw_unchecked`) build through here.
1477    ///
1478    /// [`create_extptr_sexp`]: Self::create_extptr_sexp
1479    /// [`create_extptr_sexp_unchecked`]: Self::create_extptr_sexp_unchecked
1480    #[inline]
1481    fn from_owned_parts(sexp: SEXP, cached_ptr: NonNull<RefCell<T>>, root: ProtectKey) -> Self {
1482        Self {
1483            sexp,
1484            cached_ptr,
1485            root: Some(root),
1486            _marker: PhantomData,
1487        }
1488    }
1489
1490    /// Build a *borrowed* view (`root = None`) of an SEXP R already owns.
1491    ///
1492    /// No GC root is taken and none is released — the aliased object is kept
1493    /// alive by the R-side reference that handed it to us. Used by every
1494    /// `wrap_sexp*` / `from_sexp*` / `same_object` path.
1495    #[inline]
1496    fn from_borrowed_parts(sexp: SEXP, cached_ptr: NonNull<RefCell<T>>) -> Self {
1497        Self {
1498            sexp,
1499            cached_ptr,
1500            root: None,
1501            _marker: PhantomData,
1502        }
1503    }
1504
1505    /// Root this handle's `EXTPTRSXP` for as long as the handle lives, as the
1506    /// fresh-value constructors do: for a pointer converted from a class
1507    /// handle, whose `.ptr` reference R code can drop while Rust still holds
1508    /// the pointer (a callback the call makes, or a handle Rust keeps).
1509    ///
1510    /// # Safety
1511    ///
1512    /// On R's main thread, with no root taken yet.
1513    #[inline]
1514    pub(crate) unsafe fn rooted(mut self) -> Self {
1515        if true {
    if !self.root.is_none() {
        ::core::panicking::panic("assertion failed: self.root.is_none()")
    };
};debug_assert!(self.root.is_none());
1516        self.root = Some(root_owned(self.sexp));
1517        self
1518    }
1519
1520    /// Release the pool root iff this handle owns one.
1521    ///
1522    /// Routed through [`with_r_thread`] because an owning `ExternalPtr` is
1523    /// `Send` and may be dropped on the worker thread, while the pool lives on
1524    /// R's main thread. `with_r_thread` runs the closure inline when already on
1525    /// the main thread (the common case), so this is a direct pool release
1526    /// there and a thread hand-off only from the worker. `ProtectKey` is `Copy`
1527    /// + `Send` (two `u32`s), so it crosses the boundary by value.
1528    ///
1529    /// [`with_r_thread`]: crate::worker::with_r_thread
1530    #[inline]
1531    fn release_root_if_owned(&self) {
1532        let Some(key) = self.root else {
1533            return;
1534        };
1535        crate::worker::with_r_thread(move || unroot_owned(key));
1536    }
1537
1538    /// Box `x` in its [`RefCell`] and erase that to the `Box<Box<dyn Any>>` R
1539    /// stores, returning the cell's address for `cached_ptr` as well.
1540    #[inline]
1541    fn erase(x: T) -> (NonNull<RefCell<T>>, *mut Box<dyn Any>) {
1542        let cell: *mut RefCell<T> = Box::into_raw(Box::new(RefCell::new(x)));
1543        // SAFETY: `cell` is the allocation `Box::into_raw` just released, so
1544        // `Box::from_raw` takes it back; the value stays at `cell`.
1545        let inner: Box<dyn Any> = unsafe { Box::from_raw(cell) };
1546        // SAFETY: `Box::into_raw` never returns null.
1547        let cell = unsafe { NonNull::new_unchecked(cell) };
1548        (cell, Box::into_raw(Box::new(inner)))
1549    }
1550
1551    /// Allocates memory on the heap and places `x` into it.
1552    ///
1553    /// Internally stores a `Box<Box<dyn Any>>` — a thin pointer (fits in R's
1554    /// `R_ExternalPtrAddr`) pointing to a fat pointer (carries the `Any` vtable
1555    /// for runtime type checking via `downcast`) to a `RefCell<T>`.
1556    ///
1557    /// This function can be called from the two supported R contexts:
1558    /// - If called from R's main thread, creates the ExternalPtr directly
1559    /// - If called from the worker thread (during `run_on_worker`), automatically
1560    ///   sends the R API calls to the main thread via [`with_r_thread`]
1561    ///
1562    /// # Panics
1563    ///
1564    /// Panics if called from a non-main thread outside of a `run_on_worker` context.
1565    ///
1566    /// Equivalent to `Box::new`.
1567    ///
1568    /// [`with_r_thread`]: crate::worker::with_r_thread
1569    #[inline]
1570    pub fn new(x: T) -> Self {
1571        let (cached_ptr, any_raw) = Self::erase(x);
1572
1573        // Wrap in Sendable so it can be sent across thread boundary
1574        let sendable = unsafe { sendable_any_ptr_new(any_raw) };
1575
1576        // Use with_r_thread to run R API calls on main thread. The pool root is
1577        // taken there (on the main thread, where the pool lives) and the key
1578        // crosses back by value — `(SEXP, ProtectKey)` is `Send`.
1579        let (sexp, root) = crate::worker::with_r_thread(move || {
1580            let any_raw = sendable_any_ptr_into_ptr(sendable);
1581            unsafe { Self::create_extptr_sexp_unchecked(any_raw) }
1582        });
1583
1584        Self::from_owned_parts(sexp, cached_ptr, root)
1585    }
1586
1587    /// Allocates memory on the heap and places `x` into it, without thread checks.
1588    ///
1589    /// # Safety
1590    ///
1591    /// Must be called from R's main thread. Calling from another thread
1592    /// is undefined behavior (R APIs are not thread-safe).
1593    #[inline]
1594    pub unsafe fn new_unchecked(x: T) -> Self {
1595        let (cached_ptr, any_raw) = Self::erase(x);
1596        let (sexp, root) = unsafe { Self::create_extptr_sexp_unchecked(any_raw) };
1597        Self::from_owned_parts(sexp, cached_ptr, root)
1598    }
1599
1600    /// Create an EXTPTRSXP from a `*mut Box<dyn Any>`. Must be called from main thread.
1601    ///
1602    /// The `any_raw` is a thin pointer to a heap-allocated fat pointer (`Box<dyn Any>`).
1603    /// R stores the thin pointer in `R_ExternalPtrAddr`. Returns the SEXP and the
1604    /// [`ProtectPool`] key that roots it for the owning handle's lifetime.
1605    #[inline]
1606    unsafe fn create_extptr_sexp(any_raw: *mut Box<dyn Any>) -> (SEXP, ProtectKey) {
1607        if true {
    if !!any_raw.is_null() {
        {
            ::core::panicking::panic_fmt(format_args!("create_extptr_sexp received null pointer"));
        }
    };
};debug_assert!(
1608            !any_raw.is_null(),
1609            "create_extptr_sexp received null pointer"
1610        );
1611
1612        // Own the Rust allocation until its R finalizer is registered. Temporary
1613        // roots remain lexical even when a pool root takes over on success.
1614        let allocation = unsafe { Box::from_raw(any_raw) };
1615        let scope = unsafe { ProtectScope::new() };
1616        let type_sym = unsafe { type_symbol::<T>() };
1617        let type_id_sym = unsafe { type_id_symbol::<T>() };
1618        let prot = unsafe { Rf_allocVector(SEXPTYPE::VECSXP, prot_vec_len::<T>()) };
1619        unsafe { scope.protect(prot) };
1620        prot.set_vector_elt(PROT_TYPE_ID_INDEX, type_id_sym);
1621
1622        let sexp = unsafe { R_MakeExternalPtr(ptr::null_mut(), type_sym, prot) };
1623        unsafe { scope.protect(sexp) };
1624
1625        // Non-generic finalizer — Box<dyn Any> vtable handles the concrete drop
1626        unsafe { R_RegisterCFinalizerEx(sexp, Some(release_any), Rboolean::TRUE) };
1627        // Install the address only after the finalizer is ready. Use the pointer
1628        // returned by the ownership transfer, retaining its mutable provenance.
1629        let any_raw = Box::into_raw(allocation);
1630        unsafe { R_SetExternalPtrAddr(sexp, any_raw.cast()) };
1631
1632        // Move the value's pending `Sidecar` values into `prot`, which is
1633        // protected.
1634        if T::R_SLOT_COUNT > 0 {
1635            unsafe { init_sidecars::<T>(any_raw, sexp) };
1636        }
1637
1638        // Root the owning handle for its whole Rust lifetime so it survives R
1639        // allocations while held (e.g. element-by-element in a `Vec`) before
1640        // reaching R (#836). The pool gives O(1) any-order release — see the
1641        // `EXTPTR_ROOTS` docs for why that beats `R_PreserveObject` here.
1642        // Must happen here, on the main thread, because `new` returns the SEXP
1643        // to the *calling* thread (possibly the worker) where R API is gone.
1644        let root = root_owned(sexp);
1645
1646        (sexp, root)
1647    }
1648
1649    /// Create an EXTPTRSXP from a `*mut Box<dyn Any>` without thread safety checks.
1650    ///
1651    /// # Safety
1652    ///
1653    /// Must be called from R's main thread. No debug assertions for thread safety.
1654    ///
1655    /// Returns the SEXP and the [`ProtectPool`] key that roots it.
1656    #[inline]
1657    unsafe fn create_extptr_sexp_unchecked(any_raw: *mut Box<dyn Any>) -> (SEXP, ProtectKey) {
1658        if true {
    if !!any_raw.is_null() {
        {
            ::core::panicking::panic_fmt(format_args!("create_extptr_sexp_unchecked received null pointer"));
        }
    };
};debug_assert!(
1659            !any_raw.is_null(),
1660            "create_extptr_sexp_unchecked received null pointer"
1661        );
1662
1663        let allocation = unsafe { Box::from_raw(any_raw) };
1664        let scope = unsafe { ProtectScope::new() };
1665        let type_sym = unsafe { type_symbol_unchecked::<T>() };
1666        let type_id_sym = unsafe { type_id_symbol_unchecked::<T>() };
1667
1668        let prot = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, prot_vec_len::<T>()) };
1669        unsafe { scope.protect(prot) };
1670        unsafe { prot.set_vector_elt_unchecked(PROT_TYPE_ID_INDEX, type_id_sym) };
1671
1672        let sexp = unsafe { R_MakeExternalPtr_unchecked(ptr::null_mut(), type_sym, prot) };
1673        unsafe { scope.protect(sexp) };
1674
1675        // Non-generic finalizer — Box<dyn Any> vtable handles the concrete drop
1676        unsafe {
1677            R_RegisterCFinalizerEx_unchecked(sexp, Some(release_any), Rboolean::TRUE);
1678        };
1679        let any_raw = Box::into_raw(allocation);
1680        unsafe { R_SetExternalPtrAddr_unchecked(sexp, any_raw.cast()) };
1681
1682        // Flush the value's pending `Sidecar` values (see `create_extptr_sexp`).
1683        if T::R_SLOT_COUNT > 0 {
1684            unsafe { init_sidecars::<T>(any_raw, sexp) };
1685        }
1686
1687        // Root the owning handle (see `create_extptr_sexp` for the rationale).
1688        // `root_owned` uses the pool's checked FFI, which runs inline here
1689        // because the unchecked constructors are main-thread-by-contract.
1690        let root = root_owned(sexp);
1691
1692        (sexp, root)
1693    }
1694
1695    /// Collect an iterator of values into a protected R list (`VECSXP`) holding
1696    /// one fresh external pointer per item, rooting each via the destination
1697    /// list instead of the [`ProtectPool`](crate::protect_pool).
1698    ///
1699    /// This is the GC-safe, allocation-lean way to hand many Rust values to R at
1700    /// once — e.g. converting a `Vec<T>` into an R `list()` of external pointers.
1701    /// Each `EXTPTRSXP` is created and **immediately** stored into the
1702    /// already-protected result list, so the list roots it the instant it
1703    /// exists: there is no unprotected window between element allocations, and
1704    /// **no per-element pool traffic**.
1705    ///
1706    /// Contrast the naive `items.map(ExternalPtr::new).collect::<Vec<_>>()`,
1707    /// which roots every handle in the process-wide pool (keeping the `Vec`
1708    /// GC-safe while held — #836) only to release every root again when the `Vec`
1709    /// drops, then still needs a second pass to copy the handles into a list.
1710    /// Here the list *is* the root, so both the pool round-trip and the copy
1711    /// pass are skipped. The whole batch also crosses to R's main thread in a
1712    /// single [`with_r_thread`](crate::worker::with_r_thread) hop rather than one
1713    /// per element.
1714    ///
1715    /// The returned `VECSXP` is **not** protected: the caller must protect it or
1716    /// return it to R immediately, exactly like any other freshly built SEXP
1717    /// (e.g. an [`IntoR`](crate::IntoR) result).
1718    pub fn collect_into_r_list<I>(items: I) -> SEXP
1719    where
1720        I: IntoIterator<Item = T>,
1721    {
1722        // Box + type-erase every value on the *calling* thread (no R API needed),
1723        // then hand the allocations to the main thread. Keep them owned until
1724        // each pointer's finalizer is registered: iterator or conversion panics
1725        // must drop every value that has not yet been transferred to R.
1726        let allocations: Vec<Box<dyn Any>> = items
1727            .into_iter()
1728            .map(|x| -> Box<dyn Any> { Box::new(RefCell::new(x)) })
1729            .collect();
1730        let sendable = crate::worker::Sendable(allocations);
1731
1732        crate::worker::with_r_thread(move || {
1733            let allocations = sendable.0;
1734            // SAFETY: `with_r_thread` runs this on R's main thread; every entry
1735            // owns a `RefCell<T>` in the ordinary ExternalPtr layout.
1736            unsafe { Self::build_extptr_list(allocations) }
1737        })
1738    }
1739
1740    /// Build a protected `VECSXP` of external pointers from already-erased boxes.
1741    ///
1742    /// Allocates the result list, protects it, then creates one `EXTPTRSXP` per
1743    /// entry directly into its slot — rooted by the protected list, no pool. The
1744    /// type symbols are interned once and reused (they are never GC'd, so they
1745    /// stay valid across the allocating loop). Returns the list **unprotected**.
1746    ///
1747    /// # Safety
1748    ///
1749    /// Must run on R's main thread; each allocation must wrap a `RefCell<T>`.
1750    unsafe fn build_extptr_list(allocations: Vec<Box<dyn Any>>) -> SEXP {
1751        let n = R_xlen_t::try_from(allocations.len()).expect("list length exceeds R_xlen_t::MAX");
1752        let scope = unsafe { ProtectScope::new() };
1753        let list = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, n) };
1754        unsafe { scope.protect(list) };
1755
1756        let type_sym = unsafe { type_symbol_unchecked::<T>() };
1757        let type_id_sym = unsafe { type_id_symbol_unchecked::<T>() };
1758
1759        for (i, allocation) in allocations.into_iter().enumerate() {
1760            let idx = R_xlen_t::try_from(i).expect("index exceeds R_xlen_t::MAX");
1761            // Only now allocate the outer box that gives R a thin address.
1762            let any_raw = Box::into_raw(Box::new(allocation));
1763            // SAFETY: main thread; `any_raw` owns a `T`; `list` is protected, so
1764            // it roots each element the instant `set_vector_elt` stores it.
1765            unsafe { Self::make_extptr_into_slot(any_raw, type_sym, type_id_sym, list, idx) };
1766        }
1767
1768        list
1769    }
1770
1771    /// Create an `EXTPTRSXP` for `any_raw` and store it into `dest[idx]`.
1772    ///
1773    /// Mirrors [`create_extptr_sexp_unchecked`](Self::create_extptr_sexp_unchecked)
1774    /// but roots the new pointer via `dest` (which the caller keeps protected)
1775    /// instead of the pool — the element is live the instant it lands in the
1776    /// protected list, so a bulk build pays no pool insert/release per element.
1777    ///
1778    /// # Safety
1779    ///
1780    /// Must run on R's main thread; `any_raw` must own a `T`; `dest` must be a
1781    /// protected `VECSXP` with `idx` in bounds; `type_sym` / `type_id_sym` must
1782    /// be the interned symbols for `T`.
1783    #[inline]
1784    unsafe fn make_extptr_into_slot(
1785        any_raw: *mut Box<dyn Any>,
1786        type_sym: SEXP,
1787        type_id_sym: SEXP,
1788        dest: SEXP,
1789        idx: R_xlen_t,
1790    ) {
1791        let allocation = unsafe { Box::from_raw(any_raw) };
1792        let scope = unsafe { ProtectScope::new() };
1793        let prot = unsafe { Rf_allocVector_unchecked(SEXPTYPE::VECSXP, prot_vec_len::<T>()) };
1794        unsafe { scope.protect(prot) };
1795        unsafe { prot.set_vector_elt_unchecked(PROT_TYPE_ID_INDEX, type_id_sym) };
1796
1797        let sexp = unsafe { R_MakeExternalPtr_unchecked(ptr::null_mut(), type_sym, prot) };
1798        unsafe { scope.protect(sexp) };
1799        unsafe { R_RegisterCFinalizerEx_unchecked(sexp, Some(release_any), Rboolean::TRUE) };
1800        let any_raw = Box::into_raw(allocation);
1801        unsafe { R_SetExternalPtrAddr_unchecked(sexp, any_raw.cast()) };
1802
1803        // Flush the value's pending `Sidecar` values (see `create_extptr_sexp`).
1804        if T::R_SLOT_COUNT > 0 {
1805            unsafe { init_sidecars::<T>(any_raw, sexp) };
1806        }
1807
1808        // Root via the destination list instead of the pool: `dest` is protected
1809        // by the caller, so storing `sexp` keeps it (and its `prot`) alive with
1810        // no pool churn.
1811        unsafe { dest.set_vector_elt_unchecked(idx, sexp) };
1812    }
1813
1814    /// Constructs a new `ExternalPtr` with uninitialized contents.
1815    ///
1816    /// Equivalent to `Box::new_uninit`.
1817    #[inline]
1818    pub fn new_uninit() -> ExternalPtr<MaybeUninit<T>>
1819    where
1820        MaybeUninit<T>: TypedExternal,
1821    {
1822        ExternalPtr::new(MaybeUninit::uninit())
1823    }
1824
1825    /// Constructs a new `ExternalPtr` with zeroed contents.
1826    ///
1827    /// Equivalent to `Box::new_zeroed`.
1828    #[inline]
1829    pub fn new_zeroed() -> ExternalPtr<MaybeUninit<T>>
1830    where
1831        MaybeUninit<T>: TypedExternal,
1832    {
1833        ExternalPtr::new(MaybeUninit::zeroed())
1834    }
1835
1836    /// Constructs an ExternalPtr from a raw pointer.
1837    ///
1838    /// Moves the value out of the `Box<T>` allocation into the pointer's own
1839    /// `RefCell<T>` storage and frees that allocation, so the value's address
1840    /// changes: `from_raw(into_raw(p))` round-trips the value, not the address.
1841    ///
1842    /// # Safety
1843    ///
1844    /// - `raw` must have been allocated via `Box::into_raw` or equivalent
1845    /// - `raw` must not be null
1846    /// - Caller transfers ownership to the ExternalPtr
1847    /// - Must be called from R's main thread
1848    ///
1849    /// Like `Box::from_raw`, except for the address.
1850    #[inline]
1851    pub unsafe fn from_raw(raw: *mut T) -> Self {
1852        let (cached_ptr, any_raw) = Self::erase(*unsafe { Box::from_raw(raw) });
1853        let (sexp, root) = unsafe { Self::create_extptr_sexp(any_raw) };
1854        Self::from_owned_parts(sexp, cached_ptr, root)
1855    }
1856
1857    /// Constructs an ExternalPtr from a raw pointer, without thread checks.
1858    ///
1859    /// # Safety
1860    ///
1861    /// - `raw` must have been allocated via `Box::into_raw` or equivalent
1862    /// - `raw` must not be null
1863    /// - Caller transfers ownership to the ExternalPtr
1864    /// - Must be called from R's main thread (no debug assertions)
1865    #[inline]
1866    pub unsafe fn from_raw_unchecked(raw: *mut T) -> Self {
1867        let (cached_ptr, any_raw) = Self::erase(*unsafe { Box::from_raw(raw) });
1868        let (sexp, root) = unsafe { Self::create_extptr_sexp_unchecked(any_raw) };
1869        Self::from_owned_parts(sexp, cached_ptr, root)
1870    }
1871
1872    /// Consumes the ExternalPtr, returning a raw pointer.
1873    ///
1874    /// The caller is responsible for the memory, and the finalizer is
1875    /// effectively orphaned (will do nothing since we clear the pointer).
1876    /// The R object is emptied: address, tag and protection list (each
1877    /// [`Sidecar<T>`] slot is read back into its field as a pending value
1878    /// first, so the values travel with the struct), so its accessors report
1879    /// a null external pointer, not an object restored from a saved session.
1880    ///
1881    /// The value moves out of the pointer's `RefCell<T>` storage into a new
1882    /// `Box<T>`, so its address changes. Like [`into_inner`](Self::into_inner),
1883    /// this raises the `miniextendr_borrow_conflict` error while a running call
1884    /// holds the value.
1885    ///
1886    /// Like `Box::into_raw`, except for the address.
1887    #[inline]
1888    pub fn into_raw(this: Self) -> *mut T {
1889        Box::into_raw(Box::new(Self::into_inner(this)))
1890    }
1891
1892    /// Consumes the ExternalPtr, returning a `NonNull` pointer.
1893    ///
1894    /// Equivalent to `Box::into_non_null`.
1895    #[inline]
1896    pub fn into_non_null(this: Self) -> NonNull<T> {
1897        unsafe { NonNull::new_unchecked(Self::into_raw(this)) }
1898    }
1899
1900    /// Consumes and leaks the ExternalPtr, returning a mutable reference.
1901    ///
1902    /// The memory will never be freed (from Rust's perspective; R's GC
1903    /// finalizer is neutralized).
1904    ///
1905    /// Equivalent to `Box::leak`.
1906    #[inline]
1907    pub fn leak<'a>(this: Self) -> &'a mut T
1908    where
1909        T: 'a,
1910    {
1911        unsafe { &mut *Self::into_raw(this) }
1912    }
1913
1914    /// Consumes the ExternalPtr, returning the wrapped value.
1915    ///
1916    /// Uses `Box<dyn Any>::downcast` to recover the concrete
1917    /// `Box<RefCell<T>>`, then moves the value out. The R object is emptied
1918    /// like by [`into_raw`](Self::into_raw): its accessors report a null
1919    /// external pointer afterwards, and each [`Sidecar<T>`] slot is read back
1920    /// into its field as a pending value, so wrapping the value again keeps
1921    /// the values.
1922    ///
1923    /// # Panics
1924    ///
1925    /// With the `miniextendr_borrow_conflict` R error while a running call
1926    /// (a generated method of the same object, further up the stack) holds the
1927    /// value: the call's borrow points into the storage this frees.
1928    ///
1929    /// Equivalent to `*boxed` (deref move) or `Box::into_inner`.
1930    #[inline]
1931    pub fn into_inner(this: Self) -> T {
1932        let any_raw = unsafe { R_ExternalPtrAddr(this.sexp) as *mut Box<dyn Any> };
1933        if !!any_raw.is_null() {
    {
        ::core::panicking::panic_fmt(format_args!("ExternalPtr is null or cleared"));
    }
};assert!(!any_raw.is_null(), "ExternalPtr is null or cleared");
1934        if !unsafe { stored_type_id_symbol(this.sexp) }.is_some() {
    {
        ::core::panicking::panic_fmt(format_args!("ExternalPtr has no valid storage metadata"));
    }
};assert!(
1935            unsafe { stored_type_id_symbol(this.sexp) }.is_some(),
1936            "ExternalPtr has no valid storage metadata"
1937        );
1938        if let Some(cell) = unsafe { &*any_raw }.downcast_ref::<RefCell<T>>() {
1939            drop(borrow_value_mut(cell));
1940        }
1941
1942        // The value leaves its pointer: read its slots back into the struct
1943        // as pending values, slot `i` into field `i`, so wrapping it again
1944        // keeps them.
1945        if T::R_SLOT_COUNT > 0 {
1946            // SAFETY: `cached_ptr` is valid for the handle's lifetime and no
1947            // call holds the value; `sexp` is the live pointer this handle
1948            // holds.
1949            let prot = unsafe { R_ExternalPtrProtected(this.sexp) };
1950            load_sidecars(unsafe { &*this.value_ptr() }, prot);
1951        }
1952
1953        // Ownership leaves this handle: drop our GC root before `mem::forget`.
1954        this.release_root_if_owned();
1955
1956        // Clear so finalizer is no-op
1957        unsafe { clear_moved_out(this.sexp) };
1958        mem::forget(this);
1959
1960        let outer: Box<Box<dyn Any>> = unsafe { Box::from_raw(any_raw) };
1961        let inner: Box<dyn Any> = *outer;
1962        inner
1963            .downcast::<RefCell<T>>()
1964            .expect("ExternalPtr type mismatch in into_inner")
1965            .into_inner()
1966    }
1967
1968    // region: Accessors
1969
1970    /// The value's `RefCell`.
1971    #[inline]
1972    fn cell(&self) -> &RefCell<T> {
1973        // SAFETY: cached_ptr is always valid for the lifetime of ExternalPtr,
1974        // and a shared reference to the cell is always sound.
1975        unsafe { self.cached_ptr.as_ref() }
1976    }
1977
1978    /// The value's address, through its `RefCell`.
1979    #[inline]
1980    fn value_ptr(&self) -> *mut T {
1981        // `as_ptr` gives a pointer that may be written through.
1982        self.cell().as_ptr()
1983    }
1984
1985    /// Borrows the value, as the generated wrapper does for a `&T` argument:
1986    /// until the returned view drops, a method of the same object that needs
1987    /// it mutably, a `&mut T` argument holding it, or
1988    /// [`borrow_mut`](Self::borrow_mut) through another handle raises the
1989    /// error below instead. The view also carries the R object, for the
1990    /// sidecar accessors.
1991    ///
1992    /// # Panics
1993    ///
1994    /// With the R error of class `miniextendr_borrow_conflict` while the value
1995    /// is borrowed mutably, and for a type-erased `ExternalPtr<()>`, which has
1996    /// no value of its own to borrow (downcast it first).
1997    #[inline]
1998    pub fn borrow(&self) -> ExternalPtrRef<'_, T> {
1999        ExternalPtrRef::from_guard(&self.sexp, borrow_value(self.value_cell()))
2000    }
2001
2002    /// Borrows the value mutably, as the generated wrapper does for a
2003    /// `&mut T` argument: until the returned view drops, any other borrow of
2004    /// the value raises the error below.
2005    ///
2006    /// # Panics
2007    ///
2008    /// With the R error of class `miniextendr_borrow_conflict` while the value
2009    /// is borrowed (by a running call holding it, or another handle's
2010    /// [`borrow`](Self::borrow)), and for a type-erased `ExternalPtr<()>`.
2011    #[inline]
2012    pub fn borrow_mut(&mut self) -> ExternalPtrMut<'_, T> {
2013        ExternalPtrMut::from_guard(&self.sexp, borrow_value_mut(self.value_cell()))
2014    }
2015
2016    /// The value, with no `RefCell` borrow: only the ALTREP `data1` paths,
2017    /// whose per-element callbacks read without one until #2103 measures the
2018    /// checked read.
2019    ///
2020    /// # Safety
2021    ///
2022    /// Nothing may hold the value mutably while the reference lives.
2023    #[doc(hidden)]
2024    #[inline]
2025    pub unsafe fn as_ref_unchecked(&self) -> &T {
2026        // SAFETY: the value lives as long as the handle; the caller excludes
2027        // a mutable borrow.
2028        unsafe { &*self.value_ptr() }
2029    }
2030
2031    /// The value mutably, with no `RefCell` borrow, like
2032    /// [`as_ref_unchecked`](Self::as_ref_unchecked).
2033    ///
2034    /// # Safety
2035    ///
2036    /// Nothing else may hold the value while the reference lives.
2037    #[doc(hidden)]
2038    #[inline]
2039    pub unsafe fn as_mut_unchecked(&mut self) -> &mut T {
2040        // SAFETY: as in `as_ref_unchecked`.
2041        unsafe { &mut *self.value_ptr() }
2042    }
2043
2044    /// The value's `RefCell`, which a type-erased handle doesn't point at.
2045    #[inline]
2046    fn value_cell(&self) -> &RefCell<T> {
2047        if !!is_type_erased::<T>() {
    {
        ::core::panicking::panic_fmt(format_args!("a type-erased `ExternalPtr<()>` has no value of its own to borrow; downcast it first"));
    }
};assert!(
2048            !is_type_erased::<T>(),
2049            "a type-erased `ExternalPtr<()>` has no value of its own to borrow; downcast it first"
2050        );
2051        self.cell()
2052    }
2053
2054    /// Returns the raw pointer without consuming the ExternalPtr.
2055    #[inline]
2056    pub fn as_ptr(&self) -> *const T {
2057        self.value_ptr().cast_const()
2058    }
2059
2060    /// Returns the raw mutable pointer without consuming the ExternalPtr.
2061    #[inline]
2062    pub fn as_mut_ptr(&mut self) -> *mut T {
2063        self.value_ptr()
2064    }
2065
2066    /// Checks whether two `ExternalPtr`s refer to the same allocation (pointer identity).
2067    ///
2068    /// This ignores the pointee values. Use this when you need alias detection;
2069    /// prefer `PartialEq`/`PartialOrd` for value comparisons.
2070    #[inline]
2071    pub fn ptr_eq(this: &Self, other: &Self) -> bool {
2072        ptr::eq(this.cached_ptr.as_ptr(), other.cached_ptr.as_ptr())
2073    }
2074    // endregion
2075
2076    // region: R-specific accessors
2077
2078    /// Returns the underlying SEXP.
2079    ///
2080    /// # Warning
2081    ///
2082    /// The returned SEXP must not be duplicated or the finalizer will double-free.
2083    #[inline]
2084    pub fn as_sexp(&self) -> SEXP {
2085        self.sexp
2086    }
2087
2088    /// A second handle to the same R object: no value is copied and no R
2089    /// object is allocated, and the handle takes no borrow until it is read.
2090    /// Returning it from a `#[miniextendr]` function returns the same R
2091    /// object, so R sees the identity it passed in.
2092    ///
2093    /// ```ignore
2094    /// #[miniextendr]
2095    /// pub fn first_of(objs: Vec<ExternalPtr<MyType>>) -> ExternalPtr<MyType> {
2096    ///     objs[0].same_object()
2097    /// }
2098    /// ```
2099    ///
2100    /// The new handle takes no GC root of its own: something else (the handle
2101    /// it was made from, or R) must keep the R object reachable while it is
2102    /// used.
2103    #[inline]
2104    pub fn same_object(&self) -> Self {
2105        // SAFETY: self.sexp is a valid live EXTPTRSXP that we already hold.
2106        // wrap_sexp re-extracts the data pointer from the same SEXP.
2107        unsafe { Self::wrap_sexp(self.sexp) }
2108            .expect("same_object of a live ExternalPtr should never fail")
2109    }
2110
2111    /// Returns the tag SEXP (type identifier symbol).
2112    #[inline]
2113    pub fn tag(&self) -> SEXP {
2114        unsafe { R_ExternalPtrTag(self.sexp) }
2115    }
2116
2117    /// Returns the tag SEXP (unchecked version).
2118    ///
2119    /// Skips thread safety checks for performance-critical paths.
2120    ///
2121    /// # Safety
2122    ///
2123    /// Must be called from the R main thread. Only use in ALTREP callbacks
2124    /// or other contexts where you're certain you're on the main thread.
2125    #[inline]
2126    pub unsafe fn tag_unchecked(&self) -> SEXP {
2127        unsafe { crate::sys::R_ExternalPtrTag_unchecked(self.sexp) }
2128    }
2129
2130    /// Returns the protected SEXP slot (user-protected objects).
2131    ///
2132    /// This returns the user-protected object stored in the prot VECSXP,
2133    /// not the VECSXP itself.
2134    #[inline]
2135    pub fn protected(&self) -> SEXP {
2136        unsafe {
2137            let prot = R_ExternalPtrProtected(self.sexp);
2138            if prot.is_null_or_nil() {
2139                return SEXP::nil();
2140            }
2141            if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
2142                return SEXP::nil();
2143            }
2144            prot.vector_elt(PROT_USER_INDEX)
2145        }
2146    }
2147
2148    /// Returns the protected SEXP slot (unchecked version).
2149    ///
2150    /// Skips thread safety checks for performance-critical paths.
2151    ///
2152    /// # Safety
2153    ///
2154    /// Must be called from the R main thread. Only use in ALTREP callbacks
2155    /// or other contexts where you're certain you're on the main thread.
2156    #[inline]
2157    pub unsafe fn protected_unchecked(&self) -> SEXP {
2158        use crate::sys::R_ExternalPtrProtected_unchecked;
2159
2160        unsafe {
2161            let prot = R_ExternalPtrProtected_unchecked(self.sexp);
2162            if prot.is_null_or_nil() {
2163                return SEXP::nil();
2164            }
2165            if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
2166                return SEXP::nil();
2167            }
2168            prot.vector_elt_unchecked(PROT_USER_INDEX)
2169        }
2170    }
2171
2172    /// Sets the user-protected SEXP slot.
2173    ///
2174    /// Use this to prevent R objects from being GC'd while this ExternalPtr exists.
2175    /// The type ID stored in prot slot 0 is preserved.
2176    ///
2177    /// Returns `false` if the prot structure is malformed (should not happen
2178    /// for ExternalPtrs created by this library).
2179    ///
2180    /// # Safety
2181    ///
2182    /// - `user_prot` must be a valid SEXP or R_NilValue
2183    /// - Must be called from the R main thread
2184    #[inline]
2185    pub unsafe fn set_protected(&self, user_prot: SEXP) -> bool {
2186        unsafe {
2187            let prot = R_ExternalPtrProtected(self.sexp);
2188            if prot.is_null_or_nil() {
2189                if true {
    if !false {
        {
            ::core::panicking::panic_fmt(format_args!("ExternalPtr prot slot is null or R_NilValue"));
        }
    };
};debug_assert!(false, "ExternalPtr prot slot is null or R_NilValue");
2190                return false;
2191            }
2192            if prot.type_of() != SEXPTYPE::VECSXP || prot.len() < PROT_VEC_LEN as usize {
2193                if true {
    if !false {
        {
            ::core::panicking::panic_fmt(format_args!("ExternalPtr prot slot is not a VECSXP of expected length"));
        }
    };
};debug_assert!(
2194                    false,
2195                    "ExternalPtr prot slot is not a VECSXP of expected length"
2196                );
2197                return false;
2198            }
2199            prot.set_vector_elt(PROT_USER_INDEX, user_prot);
2200            true
2201        }
2202    }
2203
2204    /// Returns the raw prot VECSXP (contains both type ID and user protected).
2205    ///
2206    /// Prefer using `protected()` for user data and `stored_type_id()` for type info.
2207    #[inline]
2208    pub fn prot_raw(&self) -> SEXP {
2209        unsafe { R_ExternalPtrProtected(self.sexp) }
2210    }
2211
2212    /// Checks if the internal pointer is null (already finalized or cleared).
2213    #[inline]
2214    pub fn is_null(&self) -> bool {
2215        unsafe { R_ExternalPtrAddr(self.sexp).is_null() }
2216    }
2217    // endregion
2218
2219    // region: Type checking
2220
2221    /// Attempt to wrap a SEXP as an ExternalPtr with type checking.
2222    ///
2223    /// Uses `Any::downcast_ref` for authoritative type checking (Rust `TypeId`).
2224    /// Type-erased `ExternalPtr<()>` deliberately skips the concrete downcast.
2225    ///
2226    /// Returns `None` if:
2227    /// - The internal pointer is null
2228    /// - The metadata does not identify ordinary miniextendr storage
2229    /// - The stored `Box<dyn Any>` does not contain a `RefCell<T>`
2230    ///
2231    /// # Safety
2232    ///
2233    /// - `sexp` must be a valid EXTPTRSXP; foreign pointers must not carry
2234    ///   forged miniextendr metadata
2235    /// - The caller must ensure no other ExternalPtr owns this SEXP
2236    pub unsafe fn wrap_sexp(sexp: SEXP) -> Option<Self> {
2237        if true {
    {
        match (&sexp.type_of(), &crate::SEXPTYPE::EXTPTRSXP) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val,
                        ::core::option::Option::Some(format_args!("wrap_sexp: expected EXTPTRSXP, got {0:?}",
                                sexp.type_of())));
                }
            }
        }
    };
};debug_assert_eq!(
2238            sexp.type_of(),
2239            crate::SEXPTYPE::EXTPTRSXP,
2240            "wrap_sexp: expected EXTPTRSXP, got {:?}",
2241            sexp.type_of()
2242        );
2243        let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
2244        if any_raw.is_null() {
2245            return None;
2246        }
2247        unsafe { stored_type_id_symbol(sexp) }?;
2248
2249        if is_type_erased::<T>() {
2250            // Type-erased path: skip downcast, just use the raw pointer
2251            // (ExternalPtr<()> doesn't care about the concrete type)
2252            return Some(Self::from_borrowed_parts(sexp, unsafe {
2253                NonNull::new_unchecked(any_raw.cast())
2254            }));
2255        }
2256
2257        let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2258        let cell: &RefCell<T> = any_box.downcast_ref::<RefCell<T>>()?;
2259        Some(Self::from_borrowed_parts(sexp, NonNull::from(cell)))
2260    }
2261
2262    /// Attempt to wrap a SEXP as an ExternalPtr (unchecked version).
2263    ///
2264    /// Skips thread safety checks for performance-critical paths like ALTREP callbacks.
2265    ///
2266    /// # Safety
2267    ///
2268    /// - `sexp` must be a valid EXTPTRSXP; foreign pointers must not carry
2269    ///   forged miniextendr metadata
2270    /// - The caller must ensure exclusive ownership
2271    /// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
2272    pub unsafe fn wrap_sexp_unchecked(sexp: SEXP) -> Option<Self> {
2273        use crate::sys::R_ExternalPtrAddr_unchecked;
2274
2275        if true {
    {
        match (&sexp.type_of(), &crate::SEXPTYPE::EXTPTRSXP) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val,
                        ::core::option::Option::Some(format_args!("wrap_sexp_unchecked: expected EXTPTRSXP, got {0:?}",
                                sexp.type_of())));
                }
            }
        }
    };
};debug_assert_eq!(
2276            sexp.type_of(),
2277            crate::SEXPTYPE::EXTPTRSXP,
2278            "wrap_sexp_unchecked: expected EXTPTRSXP, got {:?}",
2279            sexp.type_of()
2280        );
2281        let any_raw = unsafe { R_ExternalPtrAddr_unchecked(sexp) as *mut Box<dyn Any> };
2282        if any_raw.is_null() {
2283            return None;
2284        }
2285        unsafe { stored_type_id_symbol(sexp) }?;
2286
2287        if is_type_erased::<T>() {
2288            return Some(Self::from_borrowed_parts(sexp, unsafe {
2289                NonNull::new_unchecked(any_raw.cast())
2290            }));
2291        }
2292
2293        let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2294        let cell: &RefCell<T> = any_box.downcast_ref::<RefCell<T>>()?;
2295        Some(Self::from_borrowed_parts(sexp, NonNull::from(cell)))
2296    }
2297
2298    /// Attempt to wrap a SEXP as an ExternalPtr, returning an error with type info on mismatch.
2299    ///
2300    /// This is used by the [`TryFromSexp`] trait implementation.
2301    ///
2302    /// # Safety
2303    ///
2304    /// Same as [`wrap_sexp`](Self::wrap_sexp).
2305    ///
2306    /// [`TryFromSexp`]: crate::TryFromSexp
2307    pub unsafe fn wrap_sexp_with_error(sexp: SEXP) -> Result<Self, TypeMismatchError> {
2308        if true {
    {
        match (&sexp.type_of(), &crate::SEXPTYPE::EXTPTRSXP) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val,
                        ::core::option::Option::Some(format_args!("wrap_sexp_with_error: expected EXTPTRSXP, got {0:?}",
                                sexp.type_of())));
                }
            }
        }
    };
};debug_assert_eq!(
2309            sexp.type_of(),
2310            crate::SEXPTYPE::EXTPTRSXP,
2311            "wrap_sexp_with_error: expected EXTPTRSXP, got {:?}",
2312            sexp.type_of()
2313        );
2314        let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
2315        if any_raw.is_null() {
2316            // SAFETY: on R's main thread with a valid EXTPTRSXP, which the
2317            // caller roots.
2318            if unsafe { rebuild_restored::<T>(sexp, sexp) } {
2319                return unsafe { Self::wrap_sexp_with_error(sexp) };
2320            }
2321            // SAFETY: on R's main thread with a valid EXTPTRSXP.
2322            return Err(match unsafe { classify_null_address::<T>(sexp) } {
2323                NullAddress::Foreign => TypeMismatchError::NullPointer,
2324                NullAddress::Restored => TypeMismatchError::Restored {
2325                    type_name: T::TYPE_NAME,
2326                    package_classes: T::RESTORED_ERROR_CLASS,
2327                    package_message: T::RESTORED_ERROR_MESSAGE,
2328                },
2329                NullAddress::OtherVersion {
2330                    crate_name,
2331                    saved,
2332                    current,
2333                } => TypeMismatchError::OtherVersion {
2334                    type_name: T::TYPE_NAME,
2335                    crate_name,
2336                    saved,
2337                    current,
2338                    package_classes: T::RESTORED_ERROR_CLASS,
2339                    package_message: T::RESTORED_ERROR_MESSAGE,
2340                },
2341                NullAddress::OtherType { found } => TypeMismatchError::Mismatch {
2342                    expected: T::TYPE_NAME,
2343                    found,
2344                },
2345            });
2346        }
2347
2348        let stored =
2349            unsafe { stored_type_id_symbol(sexp) }.ok_or(TypeMismatchError::InvalidTypeId)?;
2350
2351        if is_type_erased::<T>() {
2352            return Ok(Self::from_borrowed_parts(sexp, unsafe {
2353                NonNull::new_unchecked(any_raw.cast())
2354            }));
2355        }
2356
2357        let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2358        match any_box.downcast_ref::<RefCell<T>>() {
2359            Some(cell) => Ok(Self::from_borrowed_parts(sexp, NonNull::from(cell))),
2360            None => {
2361                // The stored type ID, for the diagnostic.
2362                let found = symbol_name(stored);
2363                Err(TypeMismatchError::Mismatch {
2364                    expected: T::TYPE_NAME,
2365                    found,
2366                })
2367            }
2368        }
2369    }
2370
2371    /// Create an ExternalPtr from an SEXP without type checking.
2372    ///
2373    /// # Safety
2374    ///
2375    /// - `sexp` must be a valid EXTPTRSXP containing a `*mut Box<dyn Any>`
2376    ///   wrapping a `RefCell<T>`
2377    /// - The caller must ensure exclusive ownership
2378    #[inline]
2379    pub unsafe fn from_sexp_unchecked(sexp: SEXP) -> Self {
2380        if true {
    {
        match (&sexp.type_of(), &crate::SEXPTYPE::EXTPTRSXP) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val,
                        ::core::option::Option::Some(format_args!("from_sexp_unchecked: expected EXTPTRSXP, got {0:?}",
                                sexp.type_of())));
                }
            }
        }
    };
};debug_assert_eq!(
2381            sexp.type_of(),
2382            crate::SEXPTYPE::EXTPTRSXP,
2383            "from_sexp_unchecked: expected EXTPTRSXP, got {:?}",
2384            sexp.type_of()
2385        );
2386        let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
2387
2388        let cached_ptr = if is_type_erased::<T>() {
2389            // A type-erased handle never reads through `cached_ptr` (its
2390            // methods read the address afresh), so a pointer without an
2391            // address (restored from a saved session, or cleared) is accepted
2392            // here: the downcast that follows refuses it with a clear error.
2393            NonNull::new(any_raw.cast()).unwrap_or(NonNull::dangling())
2394        } else {
2395            if true {
    if !!any_raw.is_null() {
        {
            ::core::panicking::panic_fmt(format_args!("from_sexp_unchecked: null pointer"));
        }
    };
};debug_assert!(!any_raw.is_null(), "from_sexp_unchecked: null pointer");
2396            let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2397            NonNull::from(unsafe { any_box.downcast_ref::<RefCell<T>>().unwrap_unchecked() })
2398        };
2399
2400        Self::from_borrowed_parts(sexp, cached_ptr)
2401    }
2402    // endregion
2403
2404    // region: Downcast support
2405
2406    /// Returns the type name for type T.
2407    #[inline]
2408    pub fn type_name() -> &'static str {
2409        T::TYPE_NAME
2410    }
2411
2412    /// Returns the type name stored in this ExternalPtr's prot slot.
2413    ///
2414    /// Returns `None` if the prot slot doesn't contain a valid type symbol.
2415    #[inline]
2416    pub fn stored_type_name(&self) -> Option<&'static str> {
2417        // SAFETY: `self.sexp` is a valid EXTPTRSXP; on R's main thread.
2418        unsafe { stored_type_id_symbol(self.sexp) }.map(symbol_name)
2419    }
2420    // endregion
2421}
2422
2423impl ExternalPtr<()> {
2424    /// Create a type-erased ExternalPtr from an EXTPTRSXP without checking the stored type.
2425    ///
2426    /// # Safety
2427    ///
2428    /// - `sexp` must be a valid EXTPTRSXP
2429    /// - Caller must ensure exclusive ownership semantics are upheld
2430    #[inline]
2431    pub unsafe fn from_sexp(sexp: SEXP) -> Self {
2432        if true {
    if !(sexp.type_of() == SEXPTYPE::EXTPTRSXP) {
        ::core::panicking::panic("assertion failed: sexp.type_of() == SEXPTYPE::EXTPTRSXP")
    };
};debug_assert!(sexp.type_of() == SEXPTYPE::EXTPTRSXP);
2433        unsafe { Self::from_sexp_unchecked(sexp) }
2434    }
2435
2436    /// Check whether the stored `Box<dyn Any>` contains a `T`.
2437    ///
2438    /// Uses `Any::is` for authoritative runtime type checking.
2439    #[inline]
2440    pub fn is<T: TypedExternal>(&self) -> bool {
2441        self.downcast_cell::<T>().is_some()
2442    }
2443
2444    /// The `RefCell` holding the stored value if it is a `T`.
2445    ///
2446    /// Uses `Any::downcast_ref` for authoritative runtime type checking. This
2447    /// is where every generated method receiver (inherent and trait-impl) gets
2448    /// the struct: it borrows the cell for the call.
2449    #[doc(hidden)]
2450    #[inline]
2451    pub fn downcast_cell<T: TypedExternal>(&self) -> Option<&RefCell<T>> {
2452        let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
2453        if any_raw.is_null() {
2454            return None;
2455        }
2456        unsafe { stored_type_id_symbol(self.sexp) }?;
2457        let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2458        any_box.downcast_ref::<RefCell<T>>()
2459    }
2460
2461    /// Borrow the stored value if it is a `T`, like [`ExternalPtr::borrow`].
2462    ///
2463    /// Uses `Any::downcast_ref` for authoritative runtime type checking.
2464    ///
2465    /// # Panics
2466    ///
2467    /// With the R error of class `miniextendr_borrow_conflict` while the value
2468    /// is borrowed mutably.
2469    #[inline]
2470    pub fn downcast_ref<T: TypedExternal>(&self) -> Option<ExternalPtrRef<'_, T>> {
2471        self.downcast_cell::<T>()
2472            .map(|cell| ExternalPtrRef::from_guard(&self.sexp, borrow_value(cell)))
2473    }
2474
2475    /// Borrow the stored value mutably if it is a `T`, like
2476    /// [`ExternalPtr::borrow_mut`].
2477    ///
2478    /// # Panics
2479    ///
2480    /// With the R error of class `miniextendr_borrow_conflict` while the value
2481    /// is borrowed.
2482    #[inline]
2483    pub fn downcast_mut<T: TypedExternal>(&mut self) -> Option<ExternalPtrMut<'_, T>> {
2484        self.downcast_cell::<T>()
2485            .map(|cell| ExternalPtrMut::from_guard(&self.sexp, borrow_value_mut(cell)))
2486    }
2487}
2488
2489// region: Consuming (`self` by value) method support
2490
2491/// Marker left in an `EXTPTRSXP` slot while a consuming (`self` by value)
2492/// method runs, and left behind for good if that method panics.
2493///
2494/// `#[miniextendr]` methods taking bare `self` move the stored value out of
2495/// the R handle with [`ExternalPtr::<()>::take_for_consuming`], call the
2496/// method, and either write the result back
2497/// ([`ExternalPtr::<()>::restore_after_consuming`], for `self -> Self`) or
2498/// leave the slot consumed (terminal `self -> T`). A slot holding this marker
2499/// makes every later method call on the handle fail with a "consumed" error
2500/// instead of a type mismatch; the finaliser drops the marker like any value.
2501#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ConsumedSlot {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "ConsumedSlot")
    }
}Debug)]
2502pub struct ConsumedSlot;
2503
2504/// Bound for the receiver of a **fallible** consuming method
2505/// (`self -> Result<Self, E>` / `self -> Option<Self>`).
2506///
2507/// The generated wrapper calls the method on a clone of the stored value and
2508/// only overwrites the R handle on `Ok` / `Some`, so a failed step leaves the
2509/// R object exactly as it was, which is what an interactive R user expects
2510/// from `obj |> add_step(-1)` erroring. Blanket-implemented for every
2511/// `T: Clone`; the `on_unimplemented` text below is what rustc prints when
2512/// the type is not `Clone`.
2513#[diagnostic::on_unimplemented(
2514    message = "a fallible consuming method (`self -> Result<Self, E>` / `Option<Self>`) needs `{Self}: Clone`",
2515    note = "the wrapper calls the method on a clone so a failed step leaves the R object untouched; derive or implement `Clone`, or take `&mut self` and return `Result<&mut Self, E>` instead"
2516)]
2517pub trait ConsumingFallible: Clone {}
2518impl<T: Clone> ConsumingFallible for T {}
2519
2520/// Clone the stored value for a fallible consuming step (see
2521/// [`ConsumingFallible`]). Free function so codegen can name the bound.
2522#[inline]
2523pub fn clone_for_consuming<T: ConsumingFallible>(value: &T) -> T {
2524    value.clone()
2525}
2526
2527/// Panic with the right message when a handle's stored value is not a `T`:
2528/// "consumed" if a previous `self`-by-value step failed; the classed
2529/// [`RESTORED_NO_VALUE_CLASS`] / [`RESTORED_OTHER_VERSION_CLASS`] error for a
2530/// pointer built for `T` that came back from a saved session without its
2531/// value; type mismatch otherwise. Used by generated method preludes instead
2532/// of a bare `expect`.
2533#[cold]
2534pub fn handle_downcast_failed<T: TypedExternal>(ptr: &ExternalPtr<()>) -> ! {
2535    refuse_consumed_or_restored::<T>(ptr);
2536    {
    ::core::panicking::panic_fmt(format_args!("expected ExternalPtr<{0}>, found `{1}`",
            ExternalPtr::<T>::type_name(),
            ptr.stored_type_name().unwrap_or("<unknown>")));
};panic!(
2537        "expected ExternalPtr<{}>, found `{}`",
2538        ExternalPtr::<T>::type_name(),
2539        ptr.stored_type_name().unwrap_or("<unknown>")
2540    );
2541}
2542
2543/// [`handle_downcast_failed`] for a trait-impl method receiver: the same
2544/// consumed / restored errors, and a mismatch message naming the trait
2545/// method (`Trait::method()`).
2546#[cold]
2547pub fn handle_downcast_failed_in<T: TypedExternal>(ptr: &ExternalPtr<()>, trait_method: &str) -> ! {
2548    refuse_consumed_or_restored::<T>(ptr);
2549    {
    ::core::panicking::panic_fmt(format_args!("type mismatch in {0}: expected ExternalPtr<{1}>, got different type. This can happen if you pass an object of a different type to a trait method.",
            trait_method, ExternalPtr::<T>::type_name()));
};panic!(
2550        "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
2551         This can happen if you pass an object of a different type to a trait method.",
2552        trait_method,
2553        ExternalPtr::<T>::type_name()
2554    );
2555}
2556
2557/// The errors a receiver prelude raises before a type mismatch: "consumed"
2558/// for a slot a failed `self`-by-value step left, the classed restored-object
2559/// errors for a pointer built for `T` without an address.
2560fn refuse_consumed_or_restored<T: TypedExternal>(ptr: &ExternalPtr<()>) {
2561    if ptr.is_consumed() {
2562        {
    ::core::panicking::panic_fmt(format_args!("this `{0}` object was consumed by a `self`-by-value method that did not return a new value (it returned a plain result or panicked) and can no longer be used",
            ExternalPtr::<T>::type_name()));
};panic!(
2563            "this `{}` object was consumed by a `self`-by-value method that did not return \
2564             a new value (it returned a plain result or panicked) and can no longer be used",
2565            ExternalPtr::<T>::type_name()
2566        );
2567    }
2568    if ptr.has_null_address() {
2569        // SAFETY: `ptr.sexp` is a valid EXTPTRSXP without an address; on R's
2570        // main thread (the generated prelude).
2571        unsafe { refuse_restored::<T>(ptr.sexp, ptr.sexp) };
2572    }
2573}
2574
2575impl ExternalPtr<()> {
2576    /// Whether the slot holds the [`ConsumedSlot`] marker.
2577    pub fn is_consumed(&self) -> bool {
2578        let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
2579        if any_raw.is_null() || unsafe { stored_type_id_symbol(self.sexp) }.is_none() {
2580            return false;
2581        }
2582        let any_box: &Box<dyn Any> = unsafe { &*any_raw };
2583        any_box.is::<ConsumedSlot>()
2584    }
2585
2586    /// Whether the pointer has no address: restored from a saved session
2587    /// (`readRDS()` / `unserialize()`), cleared by `into_inner`, or never
2588    /// given one (R's `new("externalptr")`).
2589    pub fn has_null_address(&self) -> bool {
2590        unsafe { R_ExternalPtrAddr(self.sexp) }.is_null()
2591    }
2592
2593    /// Move the stored `T` out of the handle, leaving [`ConsumedSlot`] behind.
2594    ///
2595    /// Returns `None` when the slot does not hold a `T` (wrong type, null, or
2596    /// already consumed); the slot is untouched in that case. The outer
2597    /// `Box<Box<dyn Any>>` cell stays allocated, so the finaliser and every
2598    /// other accessor keep working on the marker. The value's [`Sidecar<T>`]
2599    /// slots stay in the pointer's `prot` list across the method: a
2600    /// `self -> Self` result that keeps the fields (`Self { n, ..self }`)
2601    /// keeps the values, one built afresh (`Self::new(..)`) replaces them at
2602    /// the write-back. Runs on R's main thread (the generated receiver
2603    /// prelude). Raises the `miniextendr_borrow_conflict` error while a
2604    /// running call holds the value.
2605    pub fn take_for_consuming<T: TypedExternal>(&mut self) -> Option<T> {
2606        let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
2607        if any_raw.is_null() {
2608            return None;
2609        }
2610        // A running call's borrow points into the storage this replaces.
2611        drop(borrow_value_mut(self.downcast_cell::<T>()?));
2612        let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
2613        let taken = std::mem::replace(any_box, Box::new(ConsumedSlot));
2614        let cell = taken
2615            .downcast::<RefCell<T>>()
2616            .expect("checked by `downcast_cell` above");
2617        Some(cell.into_inner())
2618    }
2619
2620    /// Put a value back into a slot emptied by [`Self::take_for_consuming`]
2621    /// (the write-back half of `self -> Self`). Replaces whatever the slot
2622    /// holds; the `TypedExternal` tag in the `prot` slot is unchanged because
2623    /// the type is the same. The pending values of the value's
2624    /// [`Sidecar<T>`] fields (what the method set with `Sidecar::new`) are
2625    /// flushed into `prot`, on R's main thread (a `worker` method's
2626    /// write-back runs on the worker, so the flush is routed through
2627    /// [`with_r_thread`](crate::worker::with_r_thread)); the other slots keep
2628    /// their values. Raises the `miniextendr_borrow_conflict` error while a
2629    /// running call holds the value the slot holds.
2630    pub fn restore_after_consuming<T: TypedExternal>(&mut self, value: T) {
2631        let any_raw = unsafe { R_ExternalPtrAddr(self.sexp) as *mut Box<dyn Any> };
2632        if !!any_raw.is_null() {
    {
        ::core::panicking::panic_fmt(format_args!("restore_after_consuming on a null external pointer"));
    }
};assert!(
2633            !any_raw.is_null(),
2634            "restore_after_consuming on a null external pointer"
2635        );
2636        if !unsafe { stored_type_id_symbol(self.sexp) }.is_some() {
    {
        ::core::panicking::panic_fmt(format_args!("restore_after_consuming on a foreign external pointer"));
    }
};assert!(
2637            unsafe { stored_type_id_symbol(self.sexp) }.is_some(),
2638            "restore_after_consuming on a foreign external pointer"
2639        );
2640        // A running call's borrow points into the storage this replaces.
2641        if let Some(cell) = self.downcast_cell::<T>() {
2642            drop(borrow_value_mut(cell));
2643        }
2644        let any_box: &mut Box<dyn Any> = unsafe { &mut *any_raw };
2645        *any_box = Box::new(RefCell::new(value));
2646        if T::R_SLOT_COUNT > 0 {
2647            let sexp = self.sexp;
2648            let any_raw = crate::worker::Sendable(any_raw);
2649            crate::worker::with_r_thread(move || {
2650                let any_raw = any_raw.0;
2651                // SAFETY: on R's main thread; `any_raw` owns the `T` just
2652                // stored, inside the live EXTPTRSXP `sexp`.
2653                unsafe { init_sidecars::<T>(any_raw, sexp) };
2654            });
2655        }
2656    }
2657}
2658
2659// endregion
2660
2661/// Error returned when type checking fails in `try_from_sexp_with_error`.
2662///
2663/// The `found` field in `Mismatch` contains a `&'static str` from R's
2664/// interned symbol table, which persists for the R session lifetime.
2665#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TypeMismatchError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::NullPointer =>
                ::core::fmt::Formatter::write_str(f, "NullPointer"),
            Self::InvalidTypeId =>
                ::core::fmt::Formatter::write_str(f, "InvalidTypeId"),
            Self::Mismatch { expected: __self_0, found: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "Mismatch", "expected", __self_0, "found", &__self_1),
            Self::Restored {
                type_name: __self_0,
                package_classes: __self_1,
                package_message: __self_2 } =>
                ::core::fmt::Formatter::debug_struct_field3_finish(f,
                    "Restored", "type_name", __self_0, "package_classes",
                    __self_1, "package_message", &__self_2),
            Self::OtherVersion {
                type_name: __self_0,
                crate_name: __self_1,
                saved: __self_2,
                current: __self_3,
                package_classes: __self_4,
                package_message: __self_5 } => {
                let names: &'static _ =
                    &["type_name", "crate_name", "saved", "current",
                                "package_classes", "package_message"];
                let values: &[&dyn ::core::fmt::Debug] =
                    &[__self_0, __self_1, __self_2, __self_3, __self_4,
                                &__self_5];
                ::core::fmt::Formatter::debug_struct_fields_finish(f,
                    "OtherVersion", names, values)
            }
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for TypeMismatchError {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::NullPointer => TypeMismatchError::NullPointer,
            Self::InvalidTypeId => TypeMismatchError::InvalidTypeId,
            Self::Mismatch { expected: __self_0, found: __self_1 } =>
                TypeMismatchError::Mismatch {
                    expected: ::core::clone::Clone::clone(__self_0),
                    found: ::core::clone::Clone::clone(__self_1),
                },
            Self::Restored {
                type_name: __self_0,
                package_classes: __self_1,
                package_message: __self_2 } =>
                TypeMismatchError::Restored {
                    type_name: ::core::clone::Clone::clone(__self_0),
                    package_classes: ::core::clone::Clone::clone(__self_1),
                    package_message: ::core::clone::Clone::clone(__self_2),
                },
            Self::OtherVersion {
                type_name: __self_0,
                crate_name: __self_1,
                saved: __self_2,
                current: __self_3,
                package_classes: __self_4,
                package_message: __self_5 } =>
                TypeMismatchError::OtherVersion {
                    type_name: ::core::clone::Clone::clone(__self_0),
                    crate_name: ::core::clone::Clone::clone(__self_1),
                    saved: ::core::clone::Clone::clone(__self_2),
                    current: ::core::clone::Clone::clone(__self_3),
                    package_classes: ::core::clone::Clone::clone(__self_4),
                    package_message: ::core::clone::Clone::clone(__self_5),
                },
        }
    }
}Clone)]
2666pub enum TypeMismatchError {
2667    /// The external pointer's address was null, and it was not built by
2668    /// `ExternalPtr` (R's `new("externalptr")`, a cleared pointer).
2669    NullPointer,
2670    /// The prot slot didn't contain a valid type symbol.
2671    InvalidTypeId,
2672    /// The stored type doesn't match the expected type.
2673    Mismatch {
2674        /// Expected Rust type name from this pointer wrapper.
2675        expected: &'static str,
2676        /// Actual stored Rust type name found in pointer metadata.
2677        found: &'static str,
2678    },
2679    /// A pointer this version of the crate built, restored from a saved
2680    /// session without its Rust value ([`RESTORED_NO_VALUE_CLASS`]).
2681    Restored {
2682        /// The type's name.
2683        type_name: &'static str,
2684        /// The package's classes for the type
2685        /// ([`TypedExternal::RESTORED_ERROR_CLASS`]).
2686        package_classes: &'static [&'static str],
2687        /// The package's message for the type
2688        /// ([`TypedExternal::RESTORED_ERROR_MESSAGE`]).
2689        package_message: Option<&'static str>,
2690    },
2691    /// A pointer another version of the crate saved
2692    /// ([`RESTORED_OTHER_VERSION_CLASS`]).
2693    OtherVersion {
2694        /// The type's name.
2695        type_name: &'static str,
2696        /// The crate's name.
2697        crate_name: &'static str,
2698        /// The version that saved the pointer.
2699        saved: &'static str,
2700        /// This version.
2701        current: &'static str,
2702        /// The package's classes for the type
2703        /// ([`TypedExternal::RESTORED_ERROR_CLASS`]).
2704        package_classes: &'static [&'static str],
2705        /// The package's message for the type
2706        /// ([`TypedExternal::RESTORED_ERROR_MESSAGE`]).
2707        package_message: Option<&'static str>,
2708    },
2709}
2710
2711impl TypeMismatchError {
2712    /// The condition classes of the error about a restored pointer: the
2713    /// package's classes, then the specific class, then [`RESTORED_CLASS`];
2714    /// `None` for the other variants.
2715    pub fn restored_classes(&self) -> Option<Vec<&'static str>> {
2716        let (package_classes, specific) = match self {
2717            Self::Restored {
2718                package_classes, ..
2719            } => (package_classes, RESTORED_NO_VALUE_CLASS),
2720            Self::OtherVersion {
2721                package_classes, ..
2722            } => (package_classes, RESTORED_OTHER_VERSION_CLASS),
2723            Self::NullPointer | Self::InvalidTypeId | Self::Mismatch { .. } => return None,
2724        };
2725        Some(
2726            package_classes
2727                .iter()
2728                .copied()
2729                .chain([specific, RESTORED_CLASS])
2730                .collect(),
2731        )
2732    }
2733}
2734
2735impl fmt::Display for TypeMismatchError {
2736    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2737        match self {
2738            Self::NullPointer => f.write_fmt(format_args!("external pointer is null"))write!(f, "external pointer is null"),
2739            Self::InvalidTypeId => f.write_fmt(format_args!("external pointer has no valid type id"))write!(f, "external pointer has no valid type id"),
2740            Self::Mismatch { expected, found } => {
2741                f.write_fmt(format_args!("type mismatch: expected `{0}`, found `{1}`",
        expected, found))write!(
2742                    f,
2743                    "type mismatch: expected `{}`, found `{}`",
2744                    expected, found
2745                )
2746            }
2747            Self::Restored {
2748                type_name,
2749                package_message,
2750                ..
2751            } => match package_message {
2752                Some(message) => f.write_str(message),
2753                None => f.write_str(&restored_no_value_message(type_name)),
2754            },
2755            Self::OtherVersion {
2756                type_name,
2757                crate_name,
2758                saved,
2759                current,
2760                package_message,
2761                ..
2762            } => match package_message {
2763                Some(message) => f.write_str(message),
2764                None => f.write_str(&restored_other_version_message(
2765                    type_name, crate_name, saved, current,
2766                )),
2767            },
2768        }
2769    }
2770}
2771
2772impl std::error::Error for TypeMismatchError {}
2773// endregion
2774
2775// region: MaybeUninit support
2776
2777// We need a separate TypedExternal impl for MaybeUninit<T>
2778// This is typically done via blanket impl or macro
2779
2780impl<T: TypedExternal> ExternalPtr<MaybeUninit<T>>
2781where
2782    MaybeUninit<T>: TypedExternal,
2783{
2784    /// Converts to `ExternalPtr<T>`.
2785    ///
2786    /// # Safety
2787    ///
2788    /// The value must have been initialized.
2789    ///
2790    /// # Implementation Note
2791    ///
2792    /// This method creates a *new* SEXP with `T`'s type information, leaving
2793    /// the original `MaybeUninit<T>` SEXP as an orphaned empty shell in R's heap.
2794    /// This is necessary because the type ID stored in the prot slot must match
2795    /// the actual type. The orphaned SEXP will be cleaned up by R's GC eventually.
2796    ///
2797    /// If you need to avoid this overhead, consider using `ExternalPtr<T>::new`
2798    /// directly and initializing in place via `as_mut`.
2799    ///
2800    /// Equivalent to `Box::assume_init`.
2801    #[inline]
2802    pub fn assume_init(self) -> ExternalPtr<T> {
2803        // Get the raw pointer (this clears the original SEXP, making its finalizer a no-op)
2804        let ptr = Self::into_raw(self).cast();
2805
2806        // Create a new ExternalPtr with T's type info
2807        unsafe { ExternalPtr::from_raw(ptr) }
2808    }
2809
2810    /// Writes a value and converts to initialized.
2811    ///
2812    /// Creates a new SEXP with `T`'s type information (the original
2813    /// `MaybeUninit<T>` SEXP becomes an orphaned shell, cleaned up by GC).
2814    #[inline]
2815    pub fn write(mut self, value: T) -> ExternalPtr<T> {
2816        unsafe {
2817            (*Self::as_mut_ptr(&mut self)).write(value);
2818            self.assume_init()
2819        }
2820    }
2821}
2822/// Type-erased `ExternalPtr` for cases where the concrete `T` is not needed.
2823pub type ErasedExternalPtr = ExternalPtr<()>;
2824// endregion
2825
2826// region: Trait Implementations
2827
2828impl<T: TypedExternal + Clone> Clone for ExternalPtr<T> {
2829    /// Deep clones the inner value into a new ExternalPtr.
2830    ///
2831    /// This creates a completely independent ExternalPtr with its own
2832    /// heap allocation and finalizer. The [`Sidecar<T>`] slots are copied as
2833    /// R objects (after flushing the source's pending values), so the two
2834    /// pointers share those values until one side writes a field: every
2835    /// write replaces the slot, and R copies a shared value before modifying
2836    /// it in place. For a type with `Sidecar` fields this runs on R's main
2837    /// thread only.
2838    ///
2839    /// # Panics
2840    ///
2841    /// With the R error of class `miniextendr_borrow_conflict` while the value
2842    /// is borrowed mutably, like [`borrow`](Self::borrow).
2843    #[inline]
2844    fn clone(&self) -> Self {
2845        let value = self.borrow();
2846        if T::R_SLOT_COUNT == 0 {
2847            return Self::new(T::clone(&value));
2848        }
2849        assert_sidecar_clone_on_main_thread::<T>();
2850        // SAFETY: on R's main thread; `self.sexp` is the live pointer this
2851        // handle holds, built by `ExternalPtr` for a `T`.
2852        let prot = unsafe { R_ExternalPtrProtected(self.sexp) };
2853        flush_sidecars(&*value, prot);
2854        let new = Self::new(T::clone(&value));
2855        drop(value);
2856        // SAFETY: both pointers are live and built for a `T`.
2857        unsafe { copy_sidecar_slots::<T>(self.sexp, new.sexp) };
2858        new
2859    }
2860
2861    /// Clones `source`'s value into this pointer's value in place. The
2862    /// [`Sidecar<T>`] slots are copied as R objects like in
2863    /// [`clone`](Self::clone), replacing this pointer's slot values and its
2864    /// pending values.
2865    ///
2866    /// # Panics
2867    ///
2868    /// With the R error of class `miniextendr_borrow_conflict` while
2869    /// `source`'s value is borrowed mutably or this one's is borrowed, which
2870    /// includes cloning a pointer into itself.
2871    #[inline]
2872    fn clone_from(&mut self, source: &Self) {
2873        let from = source.borrow();
2874        let mut to = self.borrow_mut();
2875        if T::R_SLOT_COUNT == 0 {
2876            T::clone_from(&mut to, &from);
2877            return;
2878        }
2879        assert_sidecar_clone_on_main_thread::<T>();
2880        // SAFETY: as in `clone`.
2881        let source_prot = unsafe { R_ExternalPtrProtected(source.sexp) };
2882        flush_sidecars(&*from, source_prot);
2883        T::clone_from(&mut to, &from);
2884        discard_pending_sidecars(&*to);
2885        drop((from, to));
2886        // SAFETY: both pointers are live and built for a `T`.
2887        unsafe { copy_sidecar_slots::<T>(source.sexp, self.sexp) };
2888    }
2889}
2890
2891/// `ExternalPtr<T>` is `Send`, and copying `Sidecar` slots is R API.
2892fn assert_sidecar_clone_on_main_thread<T: TypedExternal>() {
2893    if !crate::worker::is_r_main_thread() {
    {
        ::core::panicking::panic_fmt(format_args!("cloning an `ExternalPtr<{0}>` copies its sidecar slots, which runs on R\'s main thread only",
                T::TYPE_NAME));
    }
};assert!(
2894        crate::worker::is_r_main_thread(),
2895        "cloning an `ExternalPtr<{}>` copies its sidecar slots, which runs on R's main thread only",
2896        T::TYPE_NAME
2897    );
2898}
2899
2900impl<T: TypedExternal + Default> Default for ExternalPtr<T> {
2901    /// Creates an ExternalPtr containing the default value of T.
2902    #[inline]
2903    fn default() -> Self {
2904        Self::new(T::default())
2905    }
2906}
2907
2908/// Like `RefCell`'s: a value borrowed mutably prints as `<borrowed>`
2909/// instead of raising, and so does a type-erased `ExternalPtr<()>`, which has
2910/// no value of its own.
2911impl<T: TypedExternal + fmt::Debug> fmt::Debug for ExternalPtr<T> {
2912    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2913        if is_type_erased::<T>() {
2914            return f.write_str("<erased>");
2915        }
2916        match self.cell().try_borrow() {
2917            Ok(value) => fmt::Debug::fmt(&*value, f),
2918            Err(_) => f.write_str("<borrowed>"),
2919        }
2920    }
2921}
2922
2923/// Borrows the value, like [`ExternalPtr::borrow`].
2924impl<T: TypedExternal + fmt::Display> fmt::Display for ExternalPtr<T> {
2925    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2926        fmt::Display::fmt(&*self.borrow(), f)
2927    }
2928}
2929
2930impl<T: TypedExternal> fmt::Pointer for ExternalPtr<T> {
2931    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2932        fmt::Pointer::fmt(&Self::as_ptr(self), f)
2933    }
2934}
2935
2936/// Compares the values, borrowing both, like [`ExternalPtr::borrow`].
2937impl<T: TypedExternal + PartialEq> PartialEq for ExternalPtr<T> {
2938    #[inline]
2939    fn eq(&self, other: &Self) -> bool {
2940        *self.borrow() == *other.borrow()
2941    }
2942}
2943
2944impl<T: TypedExternal + Eq> Eq for ExternalPtr<T> {}
2945
2946impl<T: TypedExternal + PartialOrd> PartialOrd for ExternalPtr<T> {
2947    #[inline]
2948    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2949        (*self.borrow()).partial_cmp(&*other.borrow())
2950    }
2951}
2952
2953impl<T: TypedExternal + Ord> Ord for ExternalPtr<T> {
2954    #[inline]
2955    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2956        (*self.borrow()).cmp(&*other.borrow())
2957    }
2958}
2959
2960impl<T: TypedExternal + Hash> Hash for ExternalPtr<T> {
2961    #[inline]
2962    fn hash<H: Hasher>(&self, state: &mut H) {
2963        (*self.borrow()).hash(state);
2964    }
2965}
2966
2967/// Each step borrows the value mutably, like [`ExternalPtr::borrow_mut`].
2968impl<T: TypedExternal + std::iter::Iterator> std::iter::Iterator for ExternalPtr<T> {
2969    type Item = T::Item;
2970
2971    fn next(&mut self) -> Option<Self::Item> {
2972        self.borrow_mut().next()
2973    }
2974
2975    fn size_hint(&self) -> (usize, Option<usize>) {
2976        self.borrow().size_hint()
2977    }
2978
2979    fn nth(&mut self, n: usize) -> Option<Self::Item> {
2980        self.borrow_mut().nth(n)
2981    }
2982}
2983
2984impl<T: TypedExternal + std::iter::DoubleEndedIterator> std::iter::DoubleEndedIterator
2985    for ExternalPtr<T>
2986{
2987    fn next_back(&mut self) -> Option<Self::Item> {
2988        self.borrow_mut().next_back()
2989    }
2990
2991    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
2992        self.borrow_mut().nth_back(n)
2993    }
2994}
2995
2996impl<T: TypedExternal + std::iter::ExactSizeIterator> std::iter::ExactSizeIterator
2997    for ExternalPtr<T>
2998{
2999    fn len(&self) -> usize {
3000        self.borrow().len()
3001    }
3002}
3003
3004impl<T: TypedExternal + std::iter::FusedIterator> std::iter::FusedIterator for ExternalPtr<T> {}
3005
3006impl<T: TypedExternal> From<T> for ExternalPtr<T> {
3007    #[inline]
3008    fn from(t: T) -> Self {
3009        Self::new(t)
3010    }
3011}
3012
3013impl<T: TypedExternal> From<Box<T>> for ExternalPtr<T> {
3014    #[inline]
3015    fn from(boxed: Box<T>) -> Self {
3016        unsafe { Self::from_raw(Box::into_raw(boxed)) }
3017    }
3018}
3019
3020// `Drop` releases the R-side GC root taken at construction (for *owning*
3021// handles only) but never frees the pointee — that stays R's job, run by the
3022// `release_any` finalizer when R garbage-collects the `EXTPTRSXP`. Dropping the
3023// root just makes the object eligible for collection once R itself holds no
3024// other reference; if R still references it (the usual case — it was returned
3025// from a `.Call` or stored), it stays alive and the finalizer runs later.
3026//
3027// For deterministic *value* cleanup, use `ExternalPtr::into_inner` (moves the
3028// value out) or `drop(Box::from_raw(ExternalPtr::into_raw(ptr)))`.
3029impl<T: TypedExternal> Drop for ExternalPtr<T> {
3030    fn drop(&mut self) {
3031        self.release_root_if_owned();
3032    }
3033}
3034// endregion
3035
3036// region: Finalizer
3037
3038/// Guard that aborts the process if a panic starts while it is alive.
3039///
3040/// Used by [`drop_catching_panic`] to implement panic-safe destructor calls
3041/// without `catch_unwind`. When `f()` completes normally, the guard is
3042/// dropped with `std::thread::panicking()` unchanged and becomes a no-op.
3043/// If `f()` panics, the guard's destructor runs during stack unwinding
3044/// (when `std::thread::panicking()` has become `true`) and calls
3045/// `process::abort()`.
3046///
3047/// A destructor can also run *because* of an unrelated panic: a handle that
3048/// goes out of scope in a panicking `#[miniextendr]` fn is dropped by that
3049/// unwind, with `panicking()` already `true`. That is not a panic in the
3050/// destructor, so the guard records the state it started in and aborts only
3051/// on a change. (A panic in `f()` while already unwinding is a double panic,
3052/// which the Rust runtime aborts on by itself.)
3053///
3054/// This approach avoids `catch_unwind`, which registers LLVM unwind landing
3055/// pads. Inside R's GC finalizer walk, any interaction with the unwinding
3056/// machinery — especially on the first call that lazily initialises exception
3057/// handling state — can trigger an allocator call that re-enters R's GC and
3058/// produces a "recursive gc invocation" hard crash.
3059#[must_use]
3060struct AbortIfUnwinding {
3061    /// `std::thread::panicking()` when the guard was created.
3062    already_unwinding: bool,
3063}
3064
3065impl AbortIfUnwinding {
3066    #[inline]
3067    fn new() -> Self {
3068        AbortIfUnwinding {
3069            already_unwinding: std::thread::panicking(),
3070        }
3071    }
3072}
3073
3074impl Drop for AbortIfUnwinding {
3075    #[cold]
3076    fn drop(&mut self) {
3077        if !self.already_unwinding && std::thread::panicking() {
3078            // A panic propagated through a finalizer — abort immediately.
3079            // The value being dropped is in an indeterminate state; continuing
3080            // is not safe.
3081            {
    ::std::io::_eprint(format_args!("miniextendr: destructor panicked during R finalization; aborting\n"));
};eprintln!("miniextendr: destructor panicked during R finalization; aborting");
3082            std::process::abort();
3083        }
3084    }
3085}
3086
3087/// Run a destructor closure, aborting the process if the closure panics.
3088///
3089/// A panic inside a GC finalizer cannot be safely propagated: the finalizer
3090/// runs at an arbitrary point in R's garbage collector, and unwinding across
3091/// the C-ABI boundary into R's runtime is undefined behaviour. Aborting is
3092/// the only safe recovery strategy — the destructor has already left the
3093/// value in an indeterminate state, so continuing is not an option.
3094///
3095/// ## Implementation note
3096///
3097/// This function deliberately avoids `std::panic::catch_unwind`. On the first
3098/// call from within R's GC finalizer, `catch_unwind` may lazily initialise
3099/// LLVM exception-handling state, which can allocate. Any allocation during a
3100/// GC finalizer re-enters the GC and triggers the fatal "recursive gc
3101/// invocation" crash. Instead, this function uses a drop-guard that compares
3102/// `std::thread::panicking()` — a cheap, allocation-free TLS read — on entry
3103/// and on exit.
3104///
3105/// Called from a `Drop` that an unrelated panic's unwind is running, it runs
3106/// `f()` and returns: the unwind continues and reaches its `catch_unwind`
3107/// boundary (for a `#[miniextendr]` fn, the R error it becomes).
3108///
3109/// Deferred conditions queued by this destructor (including nested guards)
3110/// are suppressed on this thread. Finalization has no caller handlers to
3111/// receive them, and retaining them would strand or misattribute diagnostics.
3112///
3113/// This helper is `#[doc(hidden)]` because it is called from macro-generated
3114/// code and is not part of the public API.
3115#[doc(hidden)]
3116#[inline]
3117pub fn drop_catching_panic<F: FnOnce()>(f: F) {
3118    let _guard = AbortIfUnwinding::new();
3119    let _conditions = crate::deferred_condition::Suppress::new();
3120    f();
3121    // guard dropped here with panicking() unchanged → no-op
3122}
3123
3124/// Non-generic C finalizer called by R's garbage collector.
3125///
3126/// Since `ExternalPtr` stores `Box<Box<dyn Any>>`, the `Any` vtable carries
3127/// the concrete type's drop function. No generic parameter needed — one
3128/// finalizer function handles all `ExternalPtr<T>` types.
3129extern "C-unwind" fn release_any(sexp: SEXP) {
3130    if sexp.is_null_or_nil() {
3131        return;
3132    }
3133
3134    let any_raw = unsafe { R_ExternalPtrAddr(sexp) as *mut Box<dyn Any> };
3135
3136    // Guard against double-finalization
3137    if any_raw.is_null() {
3138        return;
3139    }
3140
3141    // Clear the external pointer first (prevents double-free if called again)
3142    unsafe { R_ClearExternalPtr(sexp) };
3143
3144    // Reconstruct the outer Box<Box<dyn Any>> and let it drop.
3145    // This drops the outer Box, then the inner Box<dyn Any>, which
3146    // uses the vtable to drop the concrete T value.
3147    //
3148    // A panicking Drop impl must not unwind across the C-ABI boundary into R.
3149    // `drop_catching_panic` catches any panic and aborts instead.
3150    drop_catching_panic(|| drop(unsafe { Box::from_raw(any_raw) }));
3151}
3152// endregion
3153
3154// region: Utility: ExternalSlice (helper for slice data)
3155
3156/// A slice stored as a standalone struct, suitable for wrapping in ExternalPtr.
3157///
3158/// This is analogous to the data inside a `Box<[T]>`, but stores capacity
3159/// for proper deallocation when created from a `Vec`.
3160///
3161/// # Usage
3162///
3163/// To use with `ExternalPtr`, implement `TypedExternal` for your specific
3164/// `ExternalSlice<YourType>`:
3165///
3166/// ```ignore
3167/// impl_typed_external!(ExternalSlice<MyElement>);
3168/// let ptr = ExternalPtr::new(ExternalSlice::new(vec![1, 2, 3]));
3169/// ```
3170#[repr(C)]
3171pub struct ExternalSlice<T: 'static> {
3172    ptr: NonNull<T>,
3173    len: usize,
3174    capacity: usize,
3175}
3176
3177impl<T: 'static> ExternalSlice<T> {
3178    /// Create an external slice from a `Vec`, preserving its allocation.
3179    pub fn new(slice: Vec<T>) -> Self {
3180        let mut vec = ManuallyDrop::new(slice);
3181        Self {
3182            ptr: unsafe { NonNull::new_unchecked(vec.as_mut_ptr()) },
3183            len: vec.len(),
3184            capacity: vec.capacity(),
3185        }
3186    }
3187
3188    /// Create from a boxed slice (capacity == len).
3189    pub fn from_boxed(boxed: Box<[T]>) -> Self {
3190        let len = boxed.len();
3191        let ptr = Box::into_raw(boxed).cast();
3192        Self {
3193            ptr: unsafe { NonNull::new_unchecked(ptr) },
3194            len,
3195            capacity: len,
3196        }
3197    }
3198
3199    /// Borrow the contents as a shared slice.
3200    pub fn as_slice(&self) -> &[T] {
3201        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
3202    }
3203
3204    /// Borrow the contents as a mutable slice.
3205    pub fn as_mut_slice(&mut self) -> &mut [T] {
3206        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
3207    }
3208
3209    /// Number of elements in the slice.
3210    pub fn len(&self) -> usize {
3211        self.len
3212    }
3213
3214    /// Returns true if the slice is empty.
3215    pub fn is_empty(&self) -> bool {
3216        self.len == 0
3217    }
3218
3219    /// Capacity of the underlying allocation.
3220    pub fn capacity(&self) -> usize {
3221        self.capacity
3222    }
3223}
3224
3225impl<T: 'static> Drop for ExternalSlice<T> {
3226    fn drop(&mut self) {
3227        unsafe {
3228            let _ = Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.capacity);
3229        }
3230    }
3231}
3232// endregion
3233
3234mod altrep_helpers;
3235pub use altrep_helpers::*;
3236
3237mod borrowed;
3238pub use borrowed::{
3239    ExternalPtrMut, ExternalPtrRef, Saved, SidecarOwner, SidecarOwnerMut, SidecarRead,
3240};
3241
3242mod sidecar;
3243pub use sidecar::*;
3244
3245#[cfg(test)]
3246mod tests {
3247    use super::drop_catching_panic;
3248
3249    #[test]
3250    fn drop_catching_panic_does_not_propagate_panic() {
3251        // Verify that drop_catching_panic catches a panicking closure and does
3252        // NOT propagate the panic to the caller.
3253        //
3254        // Note: we cannot test the abort path from inside a test process, so
3255        // we document it with a comment instead:
3256        //   If the closure panics, `drop_catching_panic` calls `eprintln!` then
3257        //   `std::process::abort()`. That path is exercised only by the process
3258        //   dying, which is observable from an external test harness (not done
3259        //   here to keep CI simple).
3260        //
3261        // What we CAN test: the happy path (no panic) completes normally, and
3262        // the function compiles and links correctly with a `FnOnce()` generic.
3263        let mut ran = false;
3264        drop_catching_panic(|| {
3265            ran = true;
3266        });
3267        assert!(ran, "closure should have been called");
3268    }
3269
3270    #[test]
3271    fn drop_catching_panic_happy_path_drops_value() {
3272        // Confirm that the closure's side-effects (i.e. actual drop) occur
3273        // when no panic is raised.
3274        use std::sync::Arc;
3275        use std::sync::atomic::{AtomicBool, Ordering};
3276
3277        let dropped = Arc::new(AtomicBool::new(false));
3278        let flag = dropped.clone();
3279
3280        struct DropSignal(Arc<AtomicBool>);
3281        impl Drop for DropSignal {
3282            fn drop(&mut self) {
3283                self.0.store(true, Ordering::SeqCst);
3284            }
3285        }
3286
3287        let signal = DropSignal(flag);
3288        drop_catching_panic(|| drop(signal));
3289
3290        assert!(
3291            dropped.load(Ordering::SeqCst),
3292            "inner value should have been dropped"
3293        );
3294    }
3295
3296    #[test]
3297    fn drop_catching_panic_in_a_drop_run_by_an_unwind_does_not_abort() {
3298        // A guarded `Drop` that runs because an unrelated panic is unwinding
3299        // (a handle going out of scope in a panicking `#[miniextendr]` fn) is
3300        // not a panic in the destructor: the unwind must continue to its
3301        // `catch_unwind`. Before #1917 this aborted the test process.
3302        use std::sync::Arc;
3303        use std::sync::atomic::{AtomicBool, Ordering};
3304
3305        struct GuardedDrop(Arc<AtomicBool>);
3306        impl Drop for GuardedDrop {
3307            fn drop(&mut self) {
3308                let ran = self.0.clone();
3309                drop_catching_panic(move || ran.store(true, Ordering::SeqCst));
3310            }
3311        }
3312
3313        let ran = Arc::new(AtomicBool::new(false));
3314        let handle = GuardedDrop(ran.clone());
3315        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
3316            let _handle = handle;
3317            panic!("unrelated panic");
3318        }));
3319
3320        assert!(result.is_err(), "the unrelated panic should propagate");
3321        assert!(
3322            ran.load(Ordering::SeqCst),
3323            "the guarded destructor should have run during the unwind"
3324        );
3325    }
3326}