Skip to main content

unwrap_class_handle

Function unwrap_class_handle 

Source
pub(crate) unsafe fn unwrap_class_handle(sexp: SEXP) -> Option<SEXP>
Expand description

Attempt to unwrap a class-wrapped handle down to the bare EXTPTRSXP it carries, so ExternalPtr::<T> argument conversion accepts the ergonomic class handle (e.g. Foo$new(...)) in addition to the raw pointer returned by a low-level constructor (audit finding A9 — audit/2026-07-03-api-sense-conversions-dataframe-errors.md #5).

Tries, in order:

  • Env / R6: a direct .ptr binding on sexp itself — most #[miniextendr(env)] classes are actually a bare classed EXTPTRSXP (the generated constructor does class(.val) <- "T" directly on the pointer returned by Rust, see env_class.rs), which already satisfies the plain EXTPTRSXP check and never reaches this function, but a user-authored environment that binds .ptr is covered here too. Then the R6 handle chain .__enclos_env__ -> private -> .ptr (R6 objects are the public environment; private only hangs off the enclosing environment stored at .__enclos_env__ for portable classes, the default — see r6_class.rs).
  • S4: the ptr slot via methods::slot() (crate::s4_helpers::s4_get_slot). Guarded by isS4(), which excludes S7 objects even though both share the S4SXP/OBJSXP SEXPTYPE — S7’s new_object(S7_object(), ...) base never sets the S4 bit.
  • Anything else carrying a .ptr attribute: S7 stores properties as plain attributes on its base object (see s7_class.rs), so Rf_getAttrib(x, ".ptr") recovers the pointer without going through S7’s @/prop() dispatch machinery.

Returns Some(inner) only when the unwrapped value is itself an EXTPTRSXP — anything else (e.g. a .ptr-named field that isn’t a pointer) is treated as “no handle found” rather than an error. No recursion beyond one unwrap level. Any::downcast remains the type-safety authority: unwrapping a handle for the wrong T still fails at the caller with the existing type-mismatch error — this only loosens the accepted R-side shape, not type safety.

§Safety

  • Must be called from R’s main thread.
  • The returned SEXP is reachable from sexp (an env binding, S4 slot, or attribute) for as long as sexp itself is protected. Macro-generated .Call() wrappers hold every argument alive in the call’s PROTECT stack for the duration of the call, so no additional protection is needed here.