Skip to main content

miniextendr

Attribute Macro miniextendr 

Source
#[miniextendr]
Expand description

Export Rust items to R.

#[miniextendr] can be applied to:

  • fn items (generate C + R wrappers)
  • impl blocks (generate R class methods)
  • trait items (generate trait ABI metadata)
  • ALTREP wrapper structs (generate RegisterAltrep impls)

§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 an Invisible<T> / Visible<T> return type; the two must agree). Only unit-NULL returns 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 &RngGuard parameter (no R argument) receives the guard to draw from
  • #[miniextendr(rng(seed = <param>))] — rng, plus the seed contract of stats::simulate() in the R wrapper (#2198), for standalone functions and s3(...) methods. <param> names the seed parameter by its Rust name. After the argument checks the wrapper creates .Random.seed if it is missing; with a non-NULL seed it calls set.seed(seed) (which validates it, so the parameter has no R-side type check) and puts the session’s .Random.seed back on exit, also after an error or a panic. The result gets a seed attribute: the seed with kind = as.list(RNGkind()), or the pre-draw .Random.seed for seed = 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 through AsSerialize<T> (requires the API serde feature); composes with return visibility markers
  • #[miniextendr(strict)] — reject lossy conversions for i64/u64/isize/usize
  • #[miniextendr(unwrap_in_r)] — return Result<T, E> to R without unwrapping
  • #[miniextendr(serde_error(tag = "..", prefix = "..", skip(..), rename(a = ".."))] — options for the serde-classed Err arm. The path itself is automatic under the API crate’s serde feature for every Result<T, E> with E: Serialize + Display. as_character("a", ..), as_integer(..), as_double(..) and as_logical(..) give payload fields that atomic R type, as the serde::as_* field helpers would from the error type itself; like skip and rename they name fields as the error serializes them, before any rename. A field takes one as_* option and is not also skipped (compile errors, all reported at once). A miniextendr_api::condition::SerdeErrorShape const 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 = PATH names another one per function: serde_error(shape = STRICT, as_double("n")) applies the declared shape, then STRICT, 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::X on a method names the impl’s type); a misspelt one is a compile error.
  • #[miniextendr(dots = typed_list!(...))] — validate dots, create dots_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 internal to R wrapper
  • #[miniextendr(noexport)] — suppresses @export from 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        # 24

Active 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 for class_any (catch-all). The generated R wrapper uses tryCatch(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_to

Note: 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.