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(Debug, Clone, Copy, PartialEq, 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 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 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(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) => format!("{message} (rebuilding it failed: {cause})"),
553 None => format!(
554 "this `{type_name}` object was restored from a saved session and rebuilding it \
555 failed: {cause}"
556 ),
557 }
558}
559
560std::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 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 _ => panic!("the stored type ID has no version"),
753 };
754 let rewritten = std::ffi::CString::new(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 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 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 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 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 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
1388thread_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 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 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 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 assert!(!any_raw.is_null(), "ExternalPtr is null or cleared");
1934 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 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 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 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 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 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 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 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 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 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(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 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 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 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 assert!(
2633 !any_raw.is_null(),
2634 "restore_after_consuming on a null external pointer"
2635 );
2636 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(Debug, 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 => write!(f, "external pointer is null"),
2739 Self::InvalidTypeId => write!(f, "external pointer has no valid type id"),
2740 Self::Mismatch { expected, found } => {
2741 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 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 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}