This guide explains how to expose Rust traits you don’t own (from external crates) to R via miniextendr’s trait ABI.

🔗The Problem

miniextendr’s #[miniextendr] attribute must be applied to trait definitions to generate the ABI metadata. You cannot retroactively annotate traits from external crates:

// This WON'T work - can't add attributes to external traits
#[miniextendr]  // ERROR: can't modify external crate
use num_traits::Num;

🔗Solution: Adapter Traits

Create a local wrapper trait that mirrors the methods you want to expose, then implement it via blanket impl for types implementing the external trait.

🔗Basic Pattern

use miniextendr_api::prelude::*;
use num_traits::Num;

// 1. Define your local adapter trait
#[miniextendr]
pub trait RNum {
    fn add(&self, other: &Self) -> Self;
    fn mul(&self, other: &Self) -> Self;
    fn is_zero(&self) -> bool;
}

// 2. Blanket impl for all types implementing the external trait
impl<T> RNum for T
where
    T: Num + Clone,
{
    fn add(&self, other: &Self) -> Self {
        self.clone() + other.clone()
    }

    fn mul(&self, other: &Self) -> Self {
        self.clone() * other.clone()
    }

    fn is_zero(&self) -> bool {
        Num::is_zero(self)
    }
}

// 3. Implement the trait for your concrete type
#[derive(ExternalPtr)]
struct MyNumber {
    value: i64,
}

#[miniextendr]
impl RNum for MyNumber {
    // Uses the blanket impl above
}

// 4. Registration is automatic - #[miniextendr] items are registered via
//    linkme distributed slices. No manual module declaration needed.

🔗Why This Works

  1. You own the adapter trait - #[miniextendr] can generate ABI metadata
  2. Blanket impl provides functionality - Any type implementing Num gets RNum
  3. Concrete impl triggers codegen - impl RNum for MyNumber generates the vtable

🔗Built-in Adapter Traits

miniextendr-api provides ready-to-use adapter traits for common std library traits. These have blanket implementations so you just need to export them for your types:

TraitWrapsR methodsRust methods
RDebugDebugdebug_str(), debug_str_pretty()same
RDisplayDisplayas_r_string()same
RFromStrFromStrfrom_str(s)r_from_str(s) -> Option<Self>
RHashHashhash()r_hash() -> String
ROrdOrdcmp(other)r_cmp(&self, other) -> i32
RPartialOrdPartialOrdpartial_cmp(other)r_partial_cmp(&self, other) -> Option<i32>
RErrorErrorerror_message(), error_chain(), error_chain_length()same
RCloneCloneclone()r_clone() -> Self
RCopyCopycopy(), is_copy()same
RDefaultDefaultdefault()r_default() -> Self

🔗Rust names differ from the R names

R calls these methods by the std names, but in Rust a method whose name std already uses carries an r_ prefix (r_clone, r_cmp, r_len, …) and keeps the std name for R through #[miniextendr(r_name = "...")]. The blanket impls cover every Clone, Default, Hash, Ord, PartialOrd and FromStr type, and RToVec covers arrays and boxed slices. With std’s names, importing one of these traits (by name or through use miniextendr_api::*;) would make the module’s own v.clone(), String::default() or a.cmp(&b) ambiguous (E0034), and [T; N]::len() would return i64. With the prefix, an import never shadows a std method.

Both empty and populated #[miniextendr] impl blocks inherit R names and skipped methods from the trait definition. An explicit impl-level r_name overrides the inherited name. The trait can be named by its defining module, its crate-root re-export, or an imported name, including a renamed import.

🔗Generic / Associated-Type Adapter Traits

These traits have generic parameters or associated types, and are supported via concrete vtable shim generation at the impl site:

TraitWrapsR methodsNotes
RIteratorIterator (associated type Item)next_item(), size_hint(), count(), collect_n(), skip(), nth()Rust next is next_item in R via r_name; next and nth return Nullable<Item>, NULL in R at the end; size_hint returns list(lower, upper) (NA upper bound when unknown)
RExtend<T>Extendextend_from_vec(), len(), is_empty()Rust r_len / r_is_empty; extend_from_slice skipped
RFromIter<T>FromIteratorfrom_vec(items)
RToVec<T>(iterable collections)to_vec(), len(), is_empty()Rust r_to_vec / r_len / r_is_empty
RMakeIter<T, I>(iterator factory)make_iter()I must implement RIterator

RIterator keeps the names size_hint, count, skip and nth: its only blanket impl is on RefCell<I>, which has no such methods and is not an iterator, so an import leaves Iterator’s methods alone.

Usage:

use miniextendr_api::prelude::*;
use miniextendr_api::{RClone, RDebug};

#[derive(Debug, Clone, ExternalPtr)]
struct MyData { value: i32 }

// Expose Debug and Clone to R: `x$RDebug$debug_str()`, `x$RClone$clone()`.
#[miniextendr]
impl RDebug for MyData {}

#[miniextendr]
impl RClone for MyData {}

// With the traits imported, `.clone()` is still `Clone::clone`; the adapter
// method is `r_clone()`.
fn copies(data: &MyData) -> (MyData, MyData) {
    (data.clone(), data.r_clone())
}

// Registration is automatic via #[miniextendr].

🔗Trait ABI Constraints

When designing adapter traits, keep these limitations in mind:

FeatureSupported?Notes
Generic parameters on traitYestrait Foo<T> - concrete shims generated at impl site
Generic methodsNofn bar<T>() not allowed
Async methodsNoasync fn not allowed
Associated typesYestype Item - resolved to concrete type at impl site
Self by valueYesfn consume(self) works
&self / &mut selfYesStandard receivers
Static methodsYesBut don’t go through vtable
#[miniextendr(skip)]YesExclude specific methods from R wrappers
#[miniextendr(r_name = "...")]YesRename a method in R (e.g., next → next_item)

Method arguments and return types must implement:

  • TryFromSexp for parameters (R → Rust conversion)
  • IntoR for return values (Rust → R conversion)

🔗Example: Using the Built-in RIterator Trait

The built-in RIterator trait has an associated type Item and uses #[miniextendr(skip)] and #[miniextendr(r_name = "...")] to customize its R interface:

use miniextendr_api::prelude::*;

#[derive(ExternalPtr)]
struct CountUp {
    current: i32,
    max: i32,
}

// RIterator has an associated type Item, resolved to i32 here.
// Its declaration supplies the R name `next_item` for Rust's `next`.
#[miniextendr]
impl miniextendr_api::adapter_traits::RIterator for CountUp {
    type Item = i32;

    fn next(&self) -> Nullable<i32> { /* ... */ }
    fn size_hint(&self) -> (i64, Option<i64>) { /* ... */ }
    fn count(&self) -> i64 { /* ... */ }
    fn collect_n(&self, n: i32) -> Vec<i32> { /* ... */ }
    fn skip(&self, n: i32) -> i32 { /* ... */ }
    fn nth(&self, n: i32) -> Nullable<i32> { /* ... */ }
}

In R, the method is called next_item() (not next(), which would shadow R’s built-in), and size_hint() returns list(lower, upper). next_item() and nth() return NULL once the iterator is exhausted, whatever the item type. An Option<i32> would give NA_integer_, which a real item could also be; Nullable<T> gives NULL for None.

🔗Alternative: Newtype Wrapper

When the external trait has complex signatures or you need explicit conversions, use a newtype wrapper instead of blanket impls:

use rust_decimal::Decimal;

// Newtype wrapper
#[derive(ExternalPtr)]
pub struct RDecimal(Decimal);

impl RDecimal {
    pub fn new(s: &str) -> Result<Self, String> {
        Decimal::from_str(s)
            .map(RDecimal)
            .map_err(|e| e.to_string())
    }

    pub fn inner(&self) -> &Decimal {
        &self.0
    }
}

// Define trait on the newtype
#[miniextendr]
pub trait DecimalOps {
    fn add(&self, other: &RDecimal) -> RDecimal;
    fn to_string(&self) -> String;
}

#[miniextendr]
impl DecimalOps for RDecimal {
    fn add(&self, other: &RDecimal) -> RDecimal {
        RDecimal(self.0 + other.0)
    }

    fn to_string(&self) -> String {
        self.0.to_string()
    }
}

🔗When to Use Newtype vs Blanket Impl

ApproachUse When
Blanket implExternal trait is simple, no associated types
NewtypeNeed explicit conversions, complex signatures, or want isolation

🔗Cross-Package Trait Dispatch

Adapter traits work with miniextendr’s cross-package trait ABI:

Producer package (defines trait + impl):

// producer/src/lib.rs
#[miniextendr]
pub trait RNum { ... }

#[derive(ExternalPtr)]
pub struct BigInt { ... }

#[miniextendr]
impl RNum for BigInt { ... }

// Registration is automatic via #[miniextendr].

Consumer package (uses trait):

// consumer/src/lib.rs
use producer::RNum;

#[miniextendr]
fn double_it(x: &dyn RNum) -> impl RNum {
    x.add(x)  // Uses trait method via vtable
}

The consumer calls RNum::add through the vtable, allowing new implementations to be added in other packages without recompiling the consumer.

🔗Complete Example

See tests/cross-package/ for a working example of:

  • producer.pkg: Defines Counter trait and SimpleCounter impl
  • consumer.pkg: Uses Counter trait objects from producer

🔗Tips

  1. Keep adapter traits small - Only expose methods you actually need in R
  2. Use concrete types - Avoid generics; use specific types like i32, f64
  3. Document the mapping - Explain how R values map to Rust types
  4. Handle errors explicitly - Return Result<T, String> for fallible operations
  5. Consider serialization - For complex external types, character (JSON/string) often works

🔗Trait-Provided Impl Expansion (TPIE)

When an adapter trait has a blanket impl, you can write an empty #[miniextendr] impl block and the trait’s default implementations are automatically used:

use miniextendr_api::prelude::*;

#[derive(Debug, Clone, ExternalPtr)]
struct MyData { value: i32 }

// Empty body - uses blanket impl from RDebug
#[miniextendr]
impl miniextendr_api::adapter_traits::RDebug for MyData {}

// Empty body - uses blanket impl from RClone
#[miniextendr]
impl miniextendr_api::adapter_traits::RClone for MyData {}

The empty body is expanded using metadata exported with the trait. A bare imported name such as RClone and the crate-root path miniextendr_api::RClone work too; companion vtable items need no imports. User-defined traits can be re-exported or renamed in the same way. The original declaration’s name still determines the R trait namespace.

This generates all the R wrappers, vtable registration, and C-callable shims without requiring you to re-implement the methods.

🔗Method Attributes

🔗#[miniextendr(skip)]

Exclude a trait method from the R interface. The method exists in Rust but gets no R wrapper:

#[miniextendr]
pub trait MyTrait {
    fn useful_method(&self) -> i32;

    #[miniextendr(skip)]
    fn internal_only(&self) -> usize;  // Not exposed to R
}

🔗#[miniextendr(r_name = "...")]

Rename a method in R to avoid conflicts with R keywords or built-ins, or give a Rust method that avoids a std name its std name in R:

#[miniextendr]
pub trait RIterator {
    #[miniextendr(r_name = "next_item")]
    fn next(&self) -> Nullable<Self::Item>;  // Called next_item() in R
}

🔗See Also

  • SAFETY.md - Thread safety for trait dispatch
  • ENTRYPOINT.md - Trait ABI initialization requirements
  • miniextendr-api/src/trait_abi/ - Trait ABI implementation