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_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 ... as the last argument. The Rust parameter becomes _dots: &Dots. Use name @ ... to give it a custom name (e.g., args @ ...args: &Dots).

§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(unsafe(main_thread))] — run on R’s main thread (bypass worker)
  • #[miniextendr(invisible)] / #[miniextendr(visible)] — control return visibility
  • #[miniextendr(check_interrupt)] — check for user interrupt after call
  • #[miniextendr(coerce)] — coerce R type before conversion (also usable per-parameter)
  • #[miniextendr(strict)] — reject lossy conversions for i64/u64/isize/usize
  • #[miniextendr(unwrap_in_r)] — return Result<T, E> to R without unwrapping
  • #[miniextendr(dots = typed_list!(...))] — validate dots, create dots_typed
  • #[miniextendr(internal)] — adds @keywords internal to R wrapper
  • #[miniextendr(noexport)] — suppresses @export from R wrapper

§Impl blocks (class systems)

Apply #[miniextendr(env|r6|s7|s3|s4)] to an impl Type block. Use #[miniextendr(label = "...")] to disambiguate multiple impl blocks on the same type. 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,y"))] - Multi-dispatch on multiple arguments
  • #[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: ExternalPtr<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

Apply #[miniextendr(class = "...", base = "...")] to a one-field wrapper struct. Registration is automatic.