Expand description
§#[derive(ExternalPtr)] - ExternalPtr Support
This module implements the #[derive(ExternalPtr)] macro which generates
a TypedExternal impl for use with ExternalPtr<T>.
Trait ABI wrapper infrastructure is automatically generated when you use
#[miniextendr] on impl Trait for Type blocks.
§Sidecar codegen vs #[miniextendr] fns
#[derive(ExternalPtr)] with #[r_data] fields emits a pair of C/R
wrappers per sidecar field (<Type>_get_<field> /
<Type>_set_<field>). These wrappers do NOT go through
c_wrapper_builder::CWrapperContext — they are hand-rolled in this
module so they can present the simplest signature R expects from a slot
accessor:
| Aspect | #[miniextendr] fn / method (c_wrapper_builder) | Sidecar accessor (here) |
|---|---|---|
| First C param | __miniextendr_call: SEXP | none |
| Last C param | — | require_live: SEXP, the restored-pointer flag (#1891) |
C numArgs | 1 + user args | 2 (getter) / 3 (setter) |
R .Call | .Call(C_…, .call = sys.call(), …) | .Call(C_…, x, FALSE) / .Call(C_…, x, value, FALSE); the field methods pass TRUE |
| Error transport | tagged-condition SEXP via with_r_unwind_protect(…, Some(call)) | tagged-condition SEXP via with_r_unwind_protect(…, None); the R guard’s sys.call() supplies attribution |
A sidecar R wrapper passes no .call argument. The C function doesn’t
have a slot for it: R would throw “Incorrect number of arguments” at
runtime (#344, #348).
The require_live flag is how the generated $ / [[ / $<- /
[[<- field methods refuse a pointer restored from a saved session while
the standalone accessors, the R6 active bindings and the S7 properties
keep reading its Sidecar values (#1891): one accessor per field, no
extra .Call(). Only a Sidecar slot’s accessors read the flag
(externalptr::sidecar_require_live); the other kinds need the live Rust
value whatever it says.
§Usage
§Basic (no traits)
#[derive(ExternalPtr)]
struct MyData {
value: i32,
}
// Generates: impl TypedExternal for MyData { ... }§With R Sidecar Slots and Class System
The #[r_data] attribute marks fields that get R accessors. Every public
slot gets the standalone Type_get_field(x) / Type_set_field(x, value),
which take the pointer or any object carrying it in .ptr (a list, an
environment, an R6 / S4 / S7 handle). #[externalptr(...)] names the class
system; the impl’s r_data_accessors option also wires the slots into the
class. Under the S3, S4 and env field syntax, $ / [[ are the class’s R
API and the standalone accessors are internal: no @export, no “Type
Sidecar Accessors” page (#1900). The derive emits both renderings in a
SidecarAccessorsEntry; the wrapper writer picks one by the class’s
ClassNameEntry::field_syntax, since the impl block is a separate
expansion. Otherwise they are exported on the type’s page.
| Class System | Attribute | R Accessors |
|---|---|---|
| Environment | #[externalptr(env)] (default) + #[miniextendr(env(r_data_accessors))] | obj$field / obj[["field"]] next to the methods, obj$field <- value |
| R6 | #[externalptr(r6)] + #[miniextendr(r6(r_data_accessors))] | Active bindings, obj$field / obj$field <- value |
| S3 | #[externalptr(s3)] + #[miniextendr(s3(r_data_accessors))] | $ / [[ / $<- / [[<- methods |
| S4 | #[externalptr(s4)] + #[miniextendr(s4(r_data_accessors))] | $ / $<- methods |
| S7 | #[externalptr(s7)] + #[miniextendr(s7(r_data_accessors))] | Properties, obj@field / obj@field <- value |
For S3, S4 and env, r_data_accessors = "get" generates the getters only.
The $ / [[ methods share the helpers generate_field_syntax_helpers
prepares for every type with R accessors (#1848). The wrapper writer includes
them only when an impl enables S3, S4 or env field syntax.
Standalone setters and R6 active-binding setters return the receiver invisibly
by default. #[r_data(setter = "visible")] exposes that return value;
setter = "invisible" explicitly requests the default. These options also
control S7 property setters, whose unmarked return remains visible.
R assignment itself stays invisible regardless of the setter’s choice.
Four field tiers are supported:
- Rooted values (
Sidecar<T>) - kept in the external pointer’s protection list, which roots them and serializes them with the pointer. The derive generates typed accessors as associated functions of the struct, with the field’s visibility, taking a view or theExternalPtrhandle (the struct holds nothing that points back at its pointer):#[r_data(ref)]a getterfn f(ptr: &impl SidecarOwner<Self>) -> T,#[r_data(mut)]a setterfn set_f(ptr: &mut impl SidecarOwnerMut<Self>, value: T),#[r_data(ref, mut)]or a bare#[r_data]both. A method that needs the values takes a view as its receiver (self: ExternalPtrRef<'_, Self>/mut self: ExternalPtrMut<'_, Self>) and callsSelf::f(&self); seeminiextendr_api::externalptr::Sidecar. A bareSEXPfield is refused: the struct can’t root it. - Scalars (
i32,f64,bool,u8) - struct fields, returned as a length-1 vector and written withRf_as* - Conversion types (anything else) - struct fields, converted with the
IntoR/TryFromSexptraits on every read and write - Computed fields (
#[r_data(get = "Self::f")] pub f: Computed, #1883) - no value in the struct; the getter calls the namedfn(&Self) -> impl IntoRon the live Rust value. There is no setter: noType_set_f(), and under the get/set field syntax an assignment raisesminiextendr_read_only_field. Onlypub, and not under#[externalptr(r6)]/#[externalptr(s7)], whose bindings and properties need a setter (#1888).
#[r_data(name = "keys")] gives a field its R name (#1891): the field
list, the $ / [[ labels, the R6 binding / S7 property and the
standalone Type_get_keys() / Type_set_keys() use it; the C symbols and
the Rust accessors keep the Rust identifier. The name must be a syntactic
R name, unique in the type, and not .ptr. A Sidecar field’s R name is
also the save format (#2215): a save names the field’s slot after it
(SIDECAR_NAMES), and a reload or a rebuild hook finds the slot by it. So
renaming the Rust field keeps earlier saves readable (give it the old
ident as its name if it had none), while renaming the R name orphans
the slot in every earlier save.
Scalar, conversion and computed values come from the Rust struct, behind
the pointer’s address, which saveRDS does not write. The R accessors of
a Sidecar field read and write the protection list by slot index (a
restored pointer’s by the field’s R name, #2187): the
setter validates the value with TryFromSexp::<T> and stores the value R
gave, and the standalone accessors keep working on a pointer readRDS()
brought back without an address (checked by the type ID in prot[0]).
§A saved and restored object
saveRDS() / serialize() keep the pointer’s prot list (the Sidecar
values and the user slot) but not the Rust value. On the restored pointer
the standalone Sidecar accessors, the R6 active bindings and the S7
properties work; the generated $ / [[ / $<- / [[<- field methods
refuse it whatever the field (they pass the accessors’ require_live
flag, #1891), and the accessor of a struct or computed field, like every
method, raises the classed error miniextendr_restored_no_value (“restored
from a saved session and has no Rust value; re-create it”). A pointer
another version of the crate saved is refused by every accessor with
miniextendr_restored_other_version, which names both versions.
#[externalptr(restored(class = ["pkg_saved", "pkg_error"], message = "..."))] puts the package’s classes in front of those (which stay) and
replaces the message, on every restored refusal of the type. Its
refuse = "every_read" (#1901) extends the field methods’ refusal to the
reads that never call an accessor: names() and an unknown $ / [[
name check the pointer first through one more C function
(.rdata_live_<Type>, externalptr::sidecar_r_live), and .DollarNames()
returns character(0) for a restored pointer. R’s y <- x
shares the one Rust value and the one prot list: a write through y is
visible through x.
#[externalptr(field_error(class = ["pkg_error_field", "pkg_error"]))]
(#1899) puts the package’s classes in front of the field readers’ own
errors: a position that picks no field (subscriptOutOfBoundsError
stays behind them, and 0 / a negative position raise it too), a pointer
of another type, and a receiver of the class that carries no pointer
(miniextendr_no_handle, which also gets the restored(class) classes).
They reach R as .rdata_error_class_<Type> and Rust as
TypedExternal::FIELD_ERROR_CLASS. field_error(no_field_hint = "...")
(#2112) adds the package’s sentence after the field list of the
miniextendr_no_field error, each %s in it the name asked for.
#[derive(ExternalPtr)]
#[externalptr(r6)]
pub struct MyType {
pub x: i32,
#[r_data]
r: RSidecar, // Selector - enables R accessors for this type
#[r_data(ref, mut)]
pub keys: Sidecar<Vec<i32>>, // In the protection list; `MyType::keys(&ptr)` / `MyType::set_keys(&mut ptr, v)`
#[r_data]
pub count: i32, // Scalar struct field
#[r_data]
pub name: String, // Conversion: uses IntoR/TryFromSexp
}
#[miniextendr(r6(r_data_accessors))]
impl MyType {
pub fn new(n: i32) -> Self {
MyType { x: 0, r: RSidecar, keys: Sidecar::new((1..=n).collect()), count: n, name: String::new() }
}
pub fn key_count(self: ExternalPtrRef<'_, Self>) -> usize {
Self::keys(&self).len()
}
}
// Generates: active bindings `keys`, `count`, `name` in the R6Class§Trait ABI wiring
#[derive(ExternalPtr)]
struct MyCounter {
value: i32,
}
#[miniextendr]
impl Counter for MyCounter { /* ... */ }§Generated Types
The runtime’s generic ErasedObject<MyCounter> owns the value and its
mx_erased header; no per-type wrapper struct or drop function is emitted.
§Base Vtable
static __MX_BASE_VTABLE_MyCounter: mx_base_vtable = mx_base_vtable {
drop: ErasedObject::<MyCounter>::drop_raw,
concrete_tag: __MX_TAG_MyCounter,
query: ErasedObject::<MyCounter>::query,
data_offset: ErasedObject::<MyCounter>::DATA_OFFSET,
};Trait implementations register their vtables with the dispatch registry.
The shared registry::universal_query looks them up by concrete type tag
and trait tag; the derive emits no per-type query function.
Structs§
- External
PtrAttrs 🔒 - What
#[externalptr(...)]says about the type. - Field
Error 🔒Options #[externalptr(field_error(class = ..., message = ..., no_field_hint = ...))].- RData
Options 🔒 - Options for a sidecar field’s generated documentation, setters and Rust accessors.
- Restored
Options 🔒 - The package’s condition classes and message for a pointer of the type
restored from a saved session,
#[externalptr(restored(class = "..." | ["...", "..."], message = "...", refuse = "every_read"))](#1891, #1901). Each is optional; the derive emitsTypedExternal::RESTORED_ERROR_CLASS/RESTORED_ERROR_MESSAGEfor the ones given. - Sidecar
Access 🔒 - Which Rust accessors a
Sidecar<T>field gets:#[r_data(ref)]the getter,#[r_data(mut)]the setter,#[r_data(ref, mut)]or a bare#[r_data]both. - Sidecar
Info 🔒 - Aggregated sidecar information extracted from struct field analysis.
- Sidecar
Slot 🔒 - Information about a single
#[r_data]-annotated sidecar slot field.
Enums§
- RData
Arg 🔒 - One item of
#[r_data(...)]:ref,mut(both keywords, soparse_nested_metacannot read them) orkey = "value". - Slot
Kind 🔒 - The kind of sidecar slot, determining how getter/setter FFI functions are generated.
Constants§
- COMPUTED_
TYPE_ 🔒NAME - The last path segment that names the marker type of a computed field,
miniextendr_api::externalptr::Computed(#1883). - SIDECAR_
TYPE_ 🔒NAME - The last path segment that names the rooted sidecar field type,
miniextendr_api::externalptr::Sidecar<T>. Its final name is #1857; the derive spells it here only.
Functions§
- computed_
getter_ 🔒path - The getter path of a
Computedfield,#[r_data(get = "Self::f")], parsed from the literal; a leadingSelfbecomes the struct’s name, since the getter is called from a free function the derive emits outside any impl. - contains_
unrooted_ 🔒r_ view - R views stored in ordinary Rust fields have no GC owner. Inspect only known views and containers: aliases and user-defined wrappers are opaque to a derive, and a generic argument alone need not be stored (PhantomData).
- derive_
external_ ptr - Main entry point for
#[derive(ExternalPtr)]. - generate_
altrep_ 🔒storage - ALTREP stores its owner in an ordinary ExternalPtr, so it needs the same slot visitor and handle accessors. Only storage is generated here: ALTREP has its own IntoR and registration, and isn’t an R class with field methods.
- generate_
class_ 🔒integration_ r_ code - Generate class-integrated R code for sidecar fields.
- generate_
erased_ 🔒wrapper - Generate the type-erased wrapper infrastructure for trait ABI dispatch.
- generate_
field_ 🔒syntax_ helpers - Generate the R helpers the
$/[[field methods ofr_data_accessorsshare on S3, S4 and env classes (#1848): the field names, a getter and a setterswitch()over the fields’.Call()s, the classedminiextendr_no_fielderror naming the fields, the classes the methods put in front of their own errors (field_error,NULLwithout them, #1899), and the liveness check ofrestored(refuse = "every_read")with the flag that gates it (FALSEand a stub without it, #1901). - generate_
getter_ 🔒body - Generate the token stream for a sidecar getter function body.
- generate_
into_ 🔒external_ ptr - Generate the
IntoExternalPtrmarker trait impl. - generate_
into_ 🔒r_ vec_ element - Generate the concrete
IntoRVecElementimpl (issue #1284). - generate_
r_ 🔒wrapper_ for_ slot - Generate R wrapper code (roxygen-annotated R functions) for a single sidecar slot.
- generate_
setter_ 🔒body - Generate the token stream for a sidecar setter function body.
- generate_
sidecar_ 🔒accessors - Generate sidecar accessor constants and
extern "C-unwind"functions. - generate_
sidecar_ 🔒rust_ accessors - Generate the Rust accessors of each
Sidecar<T>field, as associated functions of the struct taking an owner handle, with the field’s visibility:fn keys(ptr: &impl SidecarOwner<Self>) -> Tfor#[r_data(ref)],fn set_keys(ptr: &mut impl SidecarOwnerMut<Self>, value: T)for#[r_data(mut)], both for#[r_data(ref, mut)]or a bare#[r_data]. The struct holds no reference to its pointer, so there is no accessor on&self(#1856). - generate_
typed_ 🔒external - Generate the
TypedExternaltrait implementation for the derive target. - has_
r_ 🔒data_ attr - Check if a field has the
#[r_data]attribute. - is_
computed_ 🔒type - Whether a field type is
Computed, the marker of a read-only field computed from the Rust value by the function#[r_data(get = "...")]names. - is_pub 🔒
- Check if a field is public.
- is_
rsidecar_ 🔒type - Check if a field type is
RSidecar. - parse_
class_ 🔒list - The value of a
class = "..."orclass = ["...", ...]argument: the condition classes in order, non-empty, none of them empty. - parse_
externalptr_ 🔒attrs - Parse
#[externalptr(...)]attributes: the class system, the restored refusal’s classes, message and scope, and the field readers’ classes. - parse_
field_ 🔒error_ options - The arguments of
field_error(...):class = "..."orclass = ["...", ...],message = "..."andno_field_hint = "...", each once. - parse_
r_ 🔒data_ options - Parse field-level
#[r_data(ref, mut, prop_doc = "...", setter = "visible")]options. - parse_
restored_ 🔒options - The arguments of
restored(...):class = "..."orclass = ["...", ...](non-empty, in order),message = "..."(non-empty) andrefuse = "every_read", each at most once. syn refuses empty parentheses, so at least one is given. - parse_
sidecar_ 🔒info - Parse struct fields for sidecar information.
- require_
live_ 🔒param - The
require_liveparameter of a slot’s C accessors: named for the body to read when the slot uses it, underscored otherwise. - sidecar_
inner_ 🔒type - The
Tof aSidecar<T>field type,Nonefor any other type. - sidecar_
slots 🔒 - The
Sidecar<T>slots of a type, in protection-list order. - slot_
kind_ 🔒for_ type - Determine the
SlotKindfor a field type by inspecting its last path segment. - uses_
require_ 🔒live - Whether a slot’s C accessors read the
require_liveflag: only aSidecarslot can serve a restored pointer, so only its accessors decide by the flag; the other kinds need the live Rust value whatever it says.