Complete reference for #[miniextendr] on every Rust item type.

πŸ”—Dispatch Overview

#[miniextendr] adapts its behavior based on the item it’s applied to:

ItemDefault BehaviorExample
fnGenerate C wrapper + R wrapper#[miniextendr] pub fn add(x: i32, y: i32) -> i32
impl (inherent)Env-class methods#[miniextendr] impl Counter { ... }
impl Trait for TypeTrait ABI shims#[miniextendr(s3)] impl Display for Counter { ... }
traitVtable + ABI generation#[miniextendr] pub trait Counter { ... }
1-field structALTREP class#[miniextendr] struct MyVec(Vec<i32>)
multi-field structExternalPtr#[miniextendr] struct Point { x: f64, y: f64 }
fieldless enumRFactor#[miniextendr] enum Color { Red, Green, Blue }

πŸ”—Functions

#[miniextendr]
pub fn greet(name: String) -> String {
    format!("Hello, {}!", name)
}

Generates:

  • C wrapper (C_greet) handling SEXP conversion
  • R wrapper (greet <- function(name) { .Call(C_greet, name) })
  • pub functions get @export; non-pub get @noRd

πŸ”—Function Attributes

πŸ”—Visibility & Export

AttributeEffect
internalAdd @keywords internal, suppress @export
noexportSuppress @export only
invisibleWrap R return in invisible()
visibleForce visible return (override default)
doc = "..."Custom roxygen block (replaces auto-generated)
#[miniextendr(internal)]
pub fn helper() -> i32 { 42 }

#[miniextendr(invisible)]
pub fn set_option(key: String, value: i32) { /* ... */ }

πŸ”—Threading

AttributeEffect
workerRun on worker thread (default if default-worker feature)
no_workerRun on main R thread
unsafe(main_thread)Main thread, no worker overhead (for SEXP params)

Functions taking SEXP parameters automatically run on main thread.

#[miniextendr(no_worker)]
pub fn fast_add(x: i32, y: i32) -> i32 { x + y }

#[miniextendr(unsafe(main_thread))]
pub fn inspect_sexp(x: miniextendr_api::ffi::SEXP) -> i32 { /* ... */ }

πŸ”—Type Conversion

AttributeEffect
coerceAuto-coerce R types (e.g., double β†’ int)
no_coerceReject type mismatches
strictPanic on lossy conversions (i64/u64 overflow)
no_strictAllow lossy conversions
prefer = "..."Return type preference: "list", "externalptr", "vector", "native", "auto"
#[miniextendr(strict)]
pub fn exact_value(x: i64) -> i64 { x }

#[miniextendr(prefer = "list")]
pub fn get_record() -> MyStruct { /* ... */ }

πŸ”—Error Handling

AttributeEffect
error_in_rTransport Result::Err as R condition (structured error)
unwrap_in_rTransport Result::Err as R stop() message
#[miniextendr(error_in_r)]
pub fn parse_data(s: String) -> Result<i32, String> {
    s.parse::<i32>().map_err(|e| e.to_string())
}

πŸ”—Miscellaneous

AttributeEffect
check_interruptInsert R_CheckUserInterrupt() before call
rngManage R’s RNG state (GetRNGstate/PutRNGstate)
c_symbol = "..."Custom C function name
lifecycle = "..."Mark as deprecated/experimental/superseded
dots = typed_list!(...)Validate ... arguments (see DOTS_TYPED_LIST.md)
#[miniextendr(check_interrupt, rng)]
pub fn long_simulation(n: i32) -> Vec<f64> { /* ... */ }

#[miniextendr(lifecycle = "deprecated")]
pub fn old_api() -> i32 { 0 }

πŸ”—R Wrapper Customization

These attributes inject custom R code into the generated wrapper function, giving fine-grained control over the R-side behavior without touching the Rust logic.

AttributeEffect
r_name = "..."Override R function name (e.g., "is.widget")
r_entry = "..."Inject R code at function entry (before all checks)
r_post_checks = "..."Inject R code after checks (before .Call())
r_on_exit = "..."Register on.exit() cleanup (short form, add = TRUE)
r_on_exit(expr = "...", add = bool, after = bool)Long form with full on.exit() control

Generated wrapper layout:

fn_name <- function(formals) {
  # r_entry code
  on.exit(...)         # r_on_exit
  # missing defaults, lifecycle, stopifnot, match.arg
  # r_post_checks code
  .Call(C_fn_name, ...)
}
// Rename R function (C symbol still derived from Rust name)
#[miniextendr(r_name = "is.widget")]
pub fn is_widget(x: i32) -> bool { x > 0 }

// Coerce input before Rust sees it
#[miniextendr(r_entry = "x <- as.integer(x)")]
pub fn process(x: i32) -> i32 { x * 2 }

// Validate after built-in checks
#[miniextendr(r_post_checks = "stopifnot(x > 0L)")]
pub fn positive_only(x: i32) -> i32 { x }

// Register cleanup code
#[miniextendr(r_on_exit = "message(\"done\")")]
pub fn with_cleanup(x: i32) -> i32 { x + 1 }

// Full on.exit control (LIFO order)
#[miniextendr(r_on_exit(expr = "close(con)", after = false))]
pub fn with_connection(x: i32) -> i32 { x }

// Combine all four
#[miniextendr(
    r_name = "widget.create",
    r_entry = "n <- as.integer(n)",
    r_on_exit = "message(\"cleanup\")",
    r_post_checks = "stopifnot(n > 0L)",
)]
pub fn create_widget(n: i32) -> i32 { n * 10 }

r_on_exit defaults: add = TRUE, after = TRUE (composable, FIFO β€” standard R convention). When add = FALSE: omits both add and after (R ignores after when add = FALSE).

πŸ”—S3 Standalone Functions

Functions can be standalone S3 methods without an impl block:

#[miniextendr(s3(generic = "format", class = "percent"))]
pub fn format_percent(x: SEXP, _dots: ...) -> Vec<String> { /* ... */ }

πŸ”—Impl Blocks (Inherent)

#[derive(ExternalPtr)]
pub struct Counter { value: i32 }

#[miniextendr]       // default: env-class
impl Counter {
    pub fn new(initial: i32) -> Self { Counter { value: initial } }
    pub fn value(&self) -> i32 { self.value }
    pub fn increment(&mut self) { self.value += 1; }
}

πŸ”—Class System Selection

SyntaxSystemR Pattern
#[miniextendr]Env (default)obj$method() environment dispatch
#[miniextendr(r6)]R6R6Class with $new()
#[miniextendr(s3)]S3generic.Class(x, ...)
#[miniextendr(s4)]S4setClass/setMethod formal OOP
#[miniextendr(s7)]S7new_class/new_generic modern OOP
#[miniextendr(vctrs)]vctrsvctrs-compatible S3 vector class

πŸ”—Impl-Level Attributes

AttributeApplies ToEffect
class = "..."All systemsCustom R class name
label = "..."All systemsDistinguish multiple impl blocks on same type
strict / no_strictAll systemsStrict type conversion for all methods
internalAll systems@keywords internal on class
noexportAll systemsSuppress @export on class
blanketTrait implsSkip trait ABI (for blanket impls)
// Two impl blocks need labels
#[miniextendr(s3, label = "core")]
impl Counter { /* constructors + getters */ }

#[miniextendr(s3, label = "mutations")]
impl Counter { /* mutating methods */ }

πŸ”—R6-Specific Options

#[miniextendr(r6(
    inherit = "BaseClass",
    portable = true,
    cloneable = true,
    lock_objects = false,
    lock_class = false,
    r_data_accessors,
))]
impl MyClass { /* ... */ }

πŸ”—S7-Specific Options

#[miniextendr(s7(
    parent = "ParentClass",
    abstract = true,
    r_data_accessors,
))]
impl MyClass { /* ... */ }

πŸ”—vctrs-Specific Options

#[miniextendr(vctrs(
    kind = "vctr",          // vctr | rcrd | list_of
    base = "double",        // underlying R type
    inherit_base_type = true,
    ptype = "double(0)",    // prototype R expression
    abbr = "pct",           // vec_ptype_abbr
))]
impl Percent { /* ... */ }

πŸ”—Method-Level Attributes

Methods inside impl blocks can have per-method attributes nested under the class system keyword:

#[miniextendr(s3)]
impl Person {
    // Override the S3 generic name
    #[miniextendr(generic = "print")]
    pub fn show(&mut self) { println!("{}", self.name); }

    // Override the class suffix for double-dispatch
    #[miniextendr(generic = "vec_ptype2", class = "my_vctr.my_vctr")]
    pub fn ptype2_self(&self) -> SEXP { /* ... */ }

    // Generate as.data.frame.Person
    #[miniextendr(as = "data.frame")]
    pub fn as_df(&self) -> List { /* ... */ }

    // Skip this method
    #[miniextendr(ignore)]
    pub fn internal_helper(&self) { /* ... */ }

    // Parameter defaults
    #[miniextendr(defaults(n = "1"))]
    pub fn increment_by(&mut self, n: i32) { self.value += n; }
}

πŸ”—Shared Method Attributes (all class systems)

AttributeEffect
ignoreDon’t generate R wrapper for this method
constructorMark as constructor (factory method)
generic = "..."Override S3/S4/S7 generic name
class = "..."Override S3 class suffix
as = "..."Generate as.<target>() coercion method
defaults(p = "val")R-side parameter defaults
worker / no_workerThread override
check_interruptInsert interrupt check
coerce / no_coerceType coercion override
rngRNG state management
error_in_r / unwrap_in_rError handling override
r_name = "..."Override R method name
r_entry = "..."Inject R code at method entry
r_post_checks = "..."Inject R code after checks
r_on_exit = "..."Register on.exit() cleanup

Valid as = "..." targets: data.frame, list, character, numeric, double, integer, logical, matrix, vector, factor, Date, POSIXct, complex, raw, environment, function.

πŸ”—R6-Specific Method Attributes

#[miniextendr(r6)]
impl MyClass {
    // Private method (R6 convention: prefixed with .)
    #[miniextendr(private)]
    pub fn internal_calc(&self) -> f64 { /* ... */ }

    // Active binding getter
    #[miniextendr(active, prop = "count")]
    pub fn get_count(&self) -> i32 { self.count }

    // Active binding setter
    #[miniextendr(setter, prop = "count")]
    pub fn set_count(&mut self, value: i32) { self.count = value; }

    // Destructor
    #[miniextendr(finalize)]
    pub fn cleanup(&mut self) { /* ... */ }
}

πŸ”—S7-Specific Method Attributes

#[miniextendr(s7)]
impl MyClass {
    // Computed property getter
    #[miniextendr(getter, prop = "area")]
    pub fn get_area(&self) -> f64 { self.width * self.height }

    // Property setter
    #[miniextendr(setter, prop = "area")]
    pub fn set_area(&mut self, value: f64) { /* ... */ }

    // Property validator
    #[miniextendr(validate, prop = "width")]
    pub fn validate_width(value: f64) -> Result<(), String> {
        if value < 0.0 { Err("width must be non-negative".into()) } else { Ok(()) }
    }

    // Property with defaults + constraints
    #[miniextendr(required)]      // no default, must be provided
    pub fn name(&self) -> String { self.name.clone() }

    #[miniextendr(frozen)]        // immutable after creation
    pub fn id(&self) -> i32 { self.id }

    #[miniextendr(default = "0")] // R expression for default
    pub fn score(&self) -> f64 { self.score }

    // Remove ... from generic signature
    #[miniextendr(no_dots)]
    pub fn length(&self) -> i32 { self.len }

    // Multiple dispatch
    #[miniextendr(dispatch = "x,y")]
    pub fn combine(&self, other: &Self) -> Self { /* ... */ }

    // Type conversion methods
    #[miniextendr(convert_from = "OtherClass")]
    pub fn from_other(other: OtherClass) -> Self { /* ... */ }

    #[miniextendr(convert_to = "OtherClass")]
    pub fn to_other(&self) -> OtherClass { /* ... */ }
}

πŸ”—Trait Definitions

#[miniextendr]
pub trait Counter {
    fn value(&self) -> i32;
    fn increment(&mut self);
    fn default_initial() -> i32 { 0 }  // static method
}

Generates cross-package ABI:

  • TAG_COUNTER β€” Type tag constant
  • CounterVTable β€” Function pointer table
  • Method shims β€” extern "C" trampolines with with_r_unwind_protect
  • CounterView β€” Runtime dispatch wrapper

No attributes accepted on the #[miniextendr] for traits (the attr parameter is unused).

Constraints:

  • Methods must take &self or &mut self (not self by value)
  • No async methods
  • No generic method parameters (trait-level generics are OK)
  • Static methods work (resolved at compile time, not in vtable)

πŸ”—Trait Impl Blocks

#[miniextendr(s3)]
impl Counter for MyCounter {
    fn value(&self) -> i32 { self.val }
    fn increment(&mut self) { self.val += 1; }
}

Use blanket to skip ABI emission for blanket impls:

#[miniextendr(blanket)]
impl<T: AsRef<str>> Display for T { /* ... */ }

πŸ”—Structs

πŸ”—1-Field Struct β†’ ALTREP (default)

#[miniextendr]
pub struct LazyInts(Vec<i32>);

Generates ALTREP class registration + IntoR + TryFromSexp. The struct wraps a single data field and presents it as an R vector via ALTREP’s lazy evaluation.

Override with ALTREP options:

#[miniextendr(class = "MyInts", base = "integer")]
pub struct LazyInts(Vec<i32>);

Override to use a different representation:

#[miniextendr(externalptr)]
pub struct Wrapper(Vec<i32>);   // ExternalPtr instead of ALTREP

#[miniextendr(list)]
pub struct Wrapper(Vec<i32>);   // List conversion instead of ALTREP

πŸ”—Multi-Field Struct β†’ ExternalPtr (default)

#[miniextendr]
pub struct Point { x: f64, y: f64 }

Generates ExternalPtr + TypedExternal derives. The struct lives as an opaque R external pointer.

πŸ”—Struct Mode Overrides

SyntaxResult
#[miniextendr] on 1-fieldALTREP
#[miniextendr] on multi-fieldExternalPtr
#[miniextendr(list)]IntoList + TryFromList + PreferList
#[miniextendr(dataframe)]IntoList + DataFrameRow + companion type
#[miniextendr(externalptr)]ExternalPtr + TypedExternal
#[miniextendr(prefer = "list")]ExternalPtr + PreferList marker
#[miniextendr(prefer = "dataframe")]ExternalPtr + PreferDataFrame marker
#[miniextendr(prefer = "externalptr")]ExternalPtr (explicit)
#[miniextendr(prefer = "native")]ExternalPtr + PreferRNativeType marker

πŸ”—List Mode

#[miniextendr(list)]
pub struct Record {
    pub name: String,
    pub value: i32,
}

#[miniextendr]
pub fn make_record() -> Record {
    Record { name: "test".into(), value: 42 }
}
// R: list(name = "test", value = 42L)

πŸ”—DataFrame Mode

#[miniextendr(dataframe)]
pub struct Obs {
    pub id: i32,
    pub score: f64,
}

#[miniextendr]
pub fn make_obs() -> ObsDataFrame {  // companion type
    Obs::to_dataframe(vec![
        Obs { id: 1, score: 0.5 },
        Obs { id: 2, score: 0.8 },
    ])
}
// R: data.frame(id = c(1L, 2L), score = c(0.5, 0.8))

πŸ”—Enums (Fieldless)

πŸ”—Default β†’ RFactor

#[miniextendr]
#[derive(Copy, Clone)]
pub enum Season {
    Spring,
    Summer,
    Autumn,
    Winter,
}

Generates MatchArg + RFactor + IntoR + TryFromSexp. The enum maps to an R factor with levels matching variant names.

s <- get_season()  # factor("Summer", levels = c("Spring", "Summer", "Autumn", "Winter"))

πŸ”—MatchArg Mode

#[miniextendr(match_arg)]
#[derive(Copy, Clone)]
pub enum Verbosity {
    Quiet,
    Normal,
    Verbose,
}

Like RFactor but uses R’s match.arg() semantics with partial matching. The enum maps to a character scalar in R, validated against the allowed choices.

πŸ”—Explicit Factor

#[miniextendr(factor)]  // same as default, but explicit
pub enum Color { Red, Green, Blue }

πŸ”—Automatic Registration

All #[miniextendr] items are automatically registered via linkme distributed slices. No manual module declarations are needed – simply annotate your items with #[miniextendr] and they will be available to R at load time.


πŸ”—Derive Macros

These are separate from #[miniextendr] but complementary:

DeriveWhat It Generates
ExternalPtrType-safe external pointer wrapper
AltrepFull ALTREP class (IntoR + TryFromSexp + registration)
AltrepInteger / AltrepReal / …ALTREP for specific vector types
IntoListStruct β†’ R list
TryFromListR list β†’ Struct
DataFrameRowStruct β†’ columnar data.frame + companion type
RFactorEnum ↔ R factor
MatchArgEnum ↔ R character with match.arg()
RNativeTypeNewtype for native R types
PreferListMarker: route IntoR through list
PreferDataFrameMarker: route IntoR through data.frame
PreferExternalPtrMarker: route IntoR through ExternalPtr
PreferRNativeTypeMarker: route IntoR through native SEXP
Vctrsvctrs-compatible S3 class (feature-gated)

The #[miniextendr] attribute on structs/enums is a convenience that calls the appropriate derives internally. Both paths produce identical code.

πŸ”—See Also