#[miniextendr]Expand description
Export Rust items to R.
#[miniextendr] can be applied to:
fnitems (generate C + R wrappers)implblocks (generate R class methods)traititems (generate trait ABI metadata)- ALTREP wrapper structs (generate
RegisterAltrepimpls)
§Functions
use miniextendr_api::miniextendr;
#[miniextendr]
fn add(a: i32, b: i32) -> i32 { a + b }This produces a C wrapper C_<crate>_add and an R wrapper add().
Registration is automatic via linkme distributed slices.
§extern "C-unwind"
If the function is declared extern "C-unwind" and exported with
#[no_mangle] (2021), #[unsafe(no_mangle)] (2024), or #[export_name = "..."],
the function itself is the C symbol and the R wrapper is prefixed with
unsafe_ to signal bypassed safety (no worker isolation or conversion).
§Variadics (...)
Use name: ... as the last argument. It becomes a name: &Dots
parameter (args: ... → args: &Dots), through which the body reads
the dots. For dots the function ignores, write _: .... rustc rejects a
bare ... before the macro runs (the deny-by-default
varargs_without_pattern lint) and suggests _: .... The R formal is
plain ... either way.
An explicit rest: &Dots parameter is R’s ... too, at any position:
fn f(x: i32, rest: &Dots, overwrite: bool) becomes
f <- function(x, ..., overwrite), and R matches a formal after ... by
its exact name only. A function takes at most one ....
§Typed Dots Validation
Use #[miniextendr(dots = typed_list!(...))] to automatically validate dots
and create a dots_typed variable with typed accessors:
#[miniextendr(dots = typed_list!(x => numeric(), y => integer(), z? => character()))]
pub fn my_func(_: ...) -> String {
let x: f64 = dots_typed.get("x").expect("x");
let y: i32 = dots_typed.get("y").expect("y");
let z: Option<String> = dots_typed.get_opt("z").expect("z");
format!("x={}, y={}", x, y)
}Type specs: numeric(), integer(), logical(), character(), list(),
raw(), complex(), or "class_name" for class inheritance checks.
Add (n) for exact length: numeric(4). Use ? suffix for optional fields.
Use @exact; prefix for strict mode (reject extra fields).
§Attributes
#[miniextendr(worker)]— opt into worker-thread execution#[miniextendr(invisible)]/#[miniextendr(visible)]— control return visibility (identical to anInvisible<T>/Visible<T>return type; the two must agree). Only unit-NULLreturns are invisible by default; receiver-returning method tails are visible unless marked (#1213).#[miniextendr(check_interrupt)]— check for a user interrupt once, before the arguments are converted and the body runs#[miniextendr(coerce)]— coerce R type before conversion (also usable per-parameter)#[miniextendr(rng)]— load R’s RNG state before the body and save it after; a&RngGuardparameter (no R argument) receives the guard to draw from#[miniextendr(rng(seed = <param>))]—rng, plus the seed contract ofstats::simulate()in the R wrapper (#2198), for standalone functions ands3(...)methods.<param>names the seed parameter by its Rust name. After the argument checks the wrapper creates.Random.seedif it is missing; with a non-NULLseed it callsset.seed(seed)(which validates it, so the parameter has no R-side type check) and puts the session’s.Random.seedback on exit, also after an error or a panic. The result gets aseedattribute: the seed withkind = as.list(RNGkind()), or the pre-draw.Random.seedforseed = NULL; a value that can’t carry attributes is left as it is. A unit return, an impl or trait method, and a name that is no evaluated R argument are compile errors.#[miniextendr(serialize)]— serialize the complete return throughAsSerialize<T>(requires the APIserdefeature); composes with return visibility markers#[miniextendr(strict)]— reject lossy conversions for i64/u64/isize/usize#[miniextendr(unwrap_in_r)]— returnResult<T, E>to R without unwrapping#[miniextendr(serde_error(tag = "..", prefix = "..", skip(..), rename(a = ".."))]— options for the serde-classedErrarm. The path itself is automatic under the API crate’sserdefeature for everyResult<T, E>withE: Serialize + Display.as_character("a", ..),as_integer(..),as_double(..)andas_logical(..)give payload fields that atomic R type, as theserde::as_*field helpers would from the error type itself; likeskipandrenamethey name fields as the error serializes them, before any rename. A field takes oneas_*option and is not also skipped (compile errors, all reported at once). Aminiextendr_api::condition::SerdeErrorShapeconst holds any of these options;miniextendr_api::serde_error_shape!(Error = ENGINE)declares it once for an error type, and every function or method of the crate returning that type applies it first, with or without this attribute.shape = PATHnames another one per function:serde_error(shape = STRICT, as_double("n"))applies the declared shape, thenSTRICT, then the function’s own options; the later wins for a field set more than once. The path resolves from the function’s module (Self::Xon a method names the impl’s type); a misspelt one is a compile error.#[miniextendr(dots = typed_list!(...))]— validate dots, createdots_typed#[miniextendr(dots = empty | empty(allow = [..]) | forward(allow = [..]) | data)]— what...takes; the R wrapper refuses the other arguments before evaluating any, with a “did you mean” suggestion#[miniextendr(internal)]— adds@keywords internalto R wrapper#[miniextendr(noexport)]— suppresses@exportfrom R wrapper
§Declared R formals (formal)
A parameter’s R formal is its name without leading underscores. For an R
name that is no Rust identifier (row.names, na.rm, .data), declare it
with formal, on the parameter of a function or with formal(p = "..") on
an impl method. It combines with the other per-parameter options:
#[miniextendr(s3(generic = "as.data.frame", class = "fit"))]
pub fn as_data_frame_fit(
x: List,
#[miniextendr(formal = "row.names", default = "NULL")] row_names: Option<String>,
#[miniextendr(default = "FALSE")] optional: bool,
_dots: ...
) -> BuiltDataFrame { /* ... */ }
// as.data.frame.fit <- function(x, row.names = NULL, optional = FALSE, ...)
#[miniextendr(r6)]
impl Tally {
#[miniextendr(formal(na_rm = "na.rm"), defaults(na_rm = "FALSE"))]
pub fn add(&mut self, by: f64, na_rm: bool) -> f64 { /* ... */ }
}Every R-facing name uses the formal: the usage, the .Call() arguments,
the argument checks and their e$param (a failed conversion’s too), the
match_arg prelude and the generated @param lines; the method-level
options keep the Rust name as their key. The formal must be a syntactic R
name, not a reserved word, ... or ..1, not a name the generated wrapper
uses itself (.call, .val, .ptr, the S3 dispatch variables, names
starting .mx_, .miniextendr_, .rdata_ or .__MX_, a method’s
receiver), and distinct from the other formals. The dots, an NArgs
parameter, S7 property accessors and trait methods take no formal.
§Impl blocks (class systems)
Apply #[miniextendr(env|r6|s7|s3|s4)] to an impl Type block.
A type’s methods can be split over several impl blocks: they are merged into
one class. One block has the constructor and the class doc comment, and
every block repeats the class options.
Registration is automatic.
§R6 Active Bindings
For R6 classes, use #[miniextendr(r6(active))] on methods to create
active bindings (computed properties accessed without parentheses):
use miniextendr_api::miniextendr;
pub struct Rectangle {
width: f64,
height: f64,
}
#[miniextendr(r6)]
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Self { width, height }
}
/// Returns the area (width * height).
#[miniextendr(r6(active))]
pub fn area(&self) -> f64 {
self.width * self.height
}
/// Regular method (requires parentheses).
pub fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
}In R:
r <- Rectangle$new(3, 4)
r$area # 12 (active binding - no parentheses!)
r$scale(2) # Regular method call
r$area # 24Active bindings must be getter-only methods taking only &self.
§S7 Properties
For S7 classes, use #[miniextendr(s7(getter))] and #[miniextendr(s7(setter))]
to create computed properties accessed via @:
use miniextendr_api::{miniextendr, ExternalPtr};
#[derive(ExternalPtr)]
pub struct Range {
start: f64,
end: f64,
}
#[miniextendr(s7)]
impl Range {
pub fn new(start: f64, end: f64) -> Self {
Self { start, end }
}
/// Computed property (read-only): length of the range.
#[miniextendr(s7(getter))]
pub fn length(&self) -> f64 {
self.end - self.start
}
/// Dynamic property getter.
#[miniextendr(s7(getter, prop = "midpoint"))]
pub fn get_midpoint(&self) -> f64 {
(self.start + self.end) / 2.0
}
/// Dynamic property setter.
#[miniextendr(s7(setter, prop = "midpoint"))]
pub fn set_midpoint(&mut self, value: f64) {
let half = self.length() / 2.0;
self.start = value - half;
self.end = value + half;
}
}In R:
r <- Range(0, 10)
r@length # 10 (computed, read-only)
r@midpoint # 5 (dynamic property)
r@midpoint <- 20 # Adjusts start/end to center at 20§Property Attributes
#[miniextendr(s7(getter))]- Read-only computed property#[miniextendr(s7(getter, prop = "name"))]- Named property getter#[miniextendr(s7(setter, prop = "name"))]- Named property setter#[miniextendr(s7(getter, default = "0.0"))]- Property with default value#[miniextendr(s7(getter, required))]- Required property (error if not provided)#[miniextendr(s7(getter, frozen))]- Property that can only be set once#[miniextendr(s7(getter, deprecated = "Use X instead"))]- Deprecated property#[miniextendr(s7(validate))]- Validator function for property
§S7 Generic Dispatch Control
Control how S7 generics are created:
#[miniextendr(s7(no_dots))]- Create strict generic without...#[miniextendr(s7(dispatch = "x, other"))]- Multi-dispatch: the receiver (x), then the method’s leading parameters (other), in order#[miniextendr(s7(fallback))]- Register method forclass_any(catch-all). The generated R wrapper usestryCatch(x@.ptr, error = function(e) x)to safely extract the self argument, so non-miniextendr objects won’t crash with a slot-access error. Instead, incompatible objects produce a Rust type-conversion error when the method tries to interpret the argument as&Self.
#[miniextendr(s7)]
impl MyClass {
/// Strict generic: function(x) instead of function(x, ...)
#[miniextendr(s7(no_dots))]
pub fn strict_method(&self) -> i32 { 42 }
/// Fallback method dispatched on class_any.
/// Calling this on a non-MyClass object produces a type-conversion error,
/// not a slot-access crash.
#[miniextendr(s7(fallback))]
pub fn describe(&self) -> String { "generic description".into() }
}§S7 Type Conversion (convert)
Use convert_from and convert_to to enable S7’s convert() for type coercion:
use miniextendr_api::{miniextendr, ExternalPtr};
#[derive(ExternalPtr)]
pub struct Celsius { value: f64 }
#[derive(ExternalPtr)]
pub struct Fahrenheit { value: f64 }
#[miniextendr(s7)]
impl Fahrenheit {
pub fn new(value: f64) -> Self { Self { value } }
/// Convert FROM Celsius TO Fahrenheit.
/// Usage: S7::convert(celsius_obj, Fahrenheit)
#[miniextendr(s7(convert_from = "Celsius"))]
pub fn from_celsius(c: &Celsius) -> Self {
Fahrenheit { value: c.value * 9.0 / 5.0 + 32.0 }
}
/// Convert FROM Fahrenheit TO Celsius.
/// Usage: S7::convert(fahrenheit_obj, Celsius)
#[miniextendr(s7(convert_to = "Celsius"))]
pub fn to_celsius(&self) -> Celsius {
Celsius { value: (self.value - 32.0) * 5.0 / 9.0 }
}
}In R:
c <- Celsius(100)
f <- S7::convert(c, Fahrenheit) # Uses convert_from
c2 <- S7::convert(f, Celsius) # Uses convert_toNote: Classes must be defined before they can be referenced in convert methods. Define the “from” class before the “to” class to avoid forward reference issues.
§Traits (ABI)
Apply #[miniextendr] to a trait to generate ABI metadata, then use
#[miniextendr] impl Trait for Type. Registration is automatic.
§ALTREP
#[miniextendr] no longer generates ALTREP classes — class/base
attributes on a one-field struct are a compile error. Use the per-family
derives (#[derive(AltrepInteger)], …) with #[altrep(class = "...")]
instead; registration is automatic there.