Skip to main content

Module externalptr_derive

Module externalptr_derive 

Source
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: SEXPnone
Last C param—require_live: SEXP, the restored-pointer flag (#1891)
C numArgs1 + user args2 (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 transporttagged-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 SystemAttributeR 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:

  1. 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 the ExternalPtr handle (the struct holds nothing that points back at its pointer): #[r_data(ref)] a getter fn f(ptr: &impl SidecarOwner<Self>) -> T, #[r_data(mut)] a setter fn 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 calls Self::f(&self); see miniextendr_api::externalptr::Sidecar. A bare SEXP field is refused: the struct can’t root it.
  2. Scalars (i32, f64, bool, u8) - struct fields, returned as a length-1 vector and written with Rf_as*
  3. Conversion types (anything else) - struct fields, converted with the IntoR/TryFromSexp traits on every read and write
  4. Computed fields (#[r_data(get = "Self::f")] pub f: Computed, #1883) - no value in the struct; the getter calls the named fn(&Self) -> impl IntoR on the live Rust value. There is no setter: no Type_set_f(), and under the get/set field syntax an assignment raises miniextendr_read_only_field. Only pub, 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§

ExternalPtrAttrs 🔒
What #[externalptr(...)] says about the type.
FieldErrorOptions 🔒
#[externalptr(field_error(class = ..., message = ..., no_field_hint = ...))].
RDataOptions 🔒
Options for a sidecar field’s generated documentation, setters and Rust accessors.
RestoredOptions 🔒
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 emits TypedExternal::RESTORED_ERROR_CLASS / RESTORED_ERROR_MESSAGE for the ones given.
SidecarAccess 🔒
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.
SidecarInfo 🔒
Aggregated sidecar information extracted from struct field analysis.
SidecarSlot 🔒
Information about a single #[r_data]-annotated sidecar slot field.

Enums§

RDataArg 🔒
One item of #[r_data(...)]: ref, mut (both keywords, so parse_nested_meta cannot read them) or key = "value".
SlotKind 🔒
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 Computed field, #[r_data(get = "Self::f")], parsed from the literal; a leading Self becomes 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 of r_data_accessors share on S3, S4 and env classes (#1848): the field names, a getter and a setter switch() over the fields’ .Call()s, the classed miniextendr_no_field error naming the fields, the classes the methods put in front of their own errors (field_error, NULL without them, #1899), and the liveness check of restored(refuse = "every_read") with the flag that gates it (FALSE and a stub without it, #1901).
generate_getter_body 🔒
Generate the token stream for a sidecar getter function body.
generate_into_external_ptr 🔒
Generate the IntoExternalPtr marker trait impl.
generate_into_r_vec_element 🔒
Generate the concrete IntoRVecElement impl (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>) -> T for #[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 TypedExternal trait 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 = "..." or class = ["...", ...] 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 = "..." or class = ["...", ...], message = "..." and no_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 = "..." or class = ["...", ...] (non-empty, in order), message = "..." (non-empty) and refuse = "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_live parameter of a slot’s C accessors: named for the body to read when the slot uses it, underscored otherwise.
sidecar_inner_type 🔒
The T of a Sidecar<T> field type, None for any other type.
sidecar_slots 🔒
The Sidecar<T> slots of a type, in protection-list order.
slot_kind_for_type 🔒
Determine the SlotKind for a field type by inspecting its last path segment.
uses_require_live 🔒
Whether a slot’s C accessors read the require_live flag: only a Sidecar slot can serve a restored pointer, so only its accessors decide by the flag; the other kinds need the live Rust value whatever it says.