One-page reference for miniextendr’s ALTREP system.

πŸ”—Conversion Methods

πŸ”—IntoR vs IntoRAltrep

MethodConversionMemorySpeed (1M elements)Use When
.into_sexp()Copy to RR heap +3.8 MB0.44 msSmall data, R needs DATAPTR
.into_sexp_altrep()Zero-copy wrapR heap +0 MB0.20 ms (2.2x faster)Large data, lazy eval
Altrep(x)Same as aboveR heap +0 MB0.20 ms (2.2x faster)Explicit wrapper style

*Measured on Apple M-series, R 4.5

πŸ”—Quick Decision Guide

Is your data > 1000 elements?
β”œβ”€ Yes β†’ Use .into_sexp_altrep()
└─ No
   └─ Will R modify it?
      β”œβ”€ Yes β†’ Use .into_sexp() (copy)
      └─ No β†’ Either works, .into_sexp() is simpler

πŸ”—Examples

// Small data - copy is fine
#[miniextendr]
fn get_config() -> Vec<i32> {
    vec![1, 2, 3]  // Automatically copies via IntoR
}

// Large data - use ALTREP
#[miniextendr]
fn get_large_data() -> SEXP {
    let data = vec![0; 1_000_000];
    data.into_sexp_altrep()  // Zero-copy!
}

// Lazy computation - definitely ALTREP
#[miniextendr]
fn fibonacci_seq(n: i32) -> SEXP {
    fibonacci_iterator(n as usize)
        .collect::<Vec<_>>()
        .into_sexp_altrep()
}

// Range - already lazy
#[miniextendr]
fn int_range(from: i32, to: i32) -> SEXP {
    (from..to).collect::<Vec<_>>().into_sexp_altrep()
}

πŸ”—The 7 Vector Types

R TypeTraitElement TypeRequired Method
integerAltIntegerDatai32fn elt(&self, i: usize) -> i32
numericAltRealDataf64fn elt(&self, i: usize) -> f64
logicalAltLogicalDataLogicalfn elt(&self, i: usize) -> Logical
rawAltRawDatau8fn elt(&self, i: usize) -> u8
complexAltComplexDataRcomplexfn elt(&self, i: usize) -> Rcomplex
characterAltStringDataOption<&str>fn elt(&self, i: usize) -> Option<&str>
listAltListDataSEXPfn elt(&self, i: usize) -> SEXP

πŸ”—Minimal Example: Field-Based Derive (1 step)

// Everything generated from the derive - no trait impls needed
#[derive(miniextendr_api::AltrepInteger)]
#[altrep(len = "len", elt = "value", class = "MyClass")]
pub struct MyData { value: i32, len: usize }

πŸ”—Minimal Example: Manual Data Traits (2 steps)

// 1. Define the data struct with a per-family derive, opting out of the
//    auto-generated AltrepLen/Alt*Data (you write those yourself). The
//    derive still auto-emits the low-level bridge β€” no explicit
//    `impl_alt*_from_data!` call needed.
#[derive(miniextendr_api::AltrepInteger)]
#[altrep(manual, class = "MyClass")]
pub struct MyData { value: i32, len: usize }

// 2. Implement required traits
impl AltrepLen for MyData {
    fn len(&self) -> usize { self.len }
}

impl AltIntegerData for MyData {
    fn elt(&self, _i: usize) -> i32 { self.value }
}

In both cases, use MyData { value: 42, len: 100 }.into_sexp() to create the ALTREP vector.

πŸ”—Optional Methods

FeatureTrait MethodDerive KeyWhen to Use
Bulk accessget_region()-Faster than looping elt() (see ALTREP_GET_REGION.md)
DataptrAltrepDataptr<T>dataptrData in contiguous memory
SerializationAltrepSerializeserializeSave/load support needed
SubsettingAltrepExtractSubset::extract_subset()subsetO(1) subset possible (integer/complex only)
Mutationlow-level AltString/AltList::set_elt()none β€” manual, no_lowlevel + hand-written low-level traitsMutable String/List (see ALTREP.md Β§ Mutable Vectors)
NA hintno_na()-Enables optimizations
Sorted hintis_sorted()-Enables optimizations
Sumsum()-O(1) computation possible
Minmin()-O(1) computation possible
Maxmax()-O(1) computation possible

πŸ”—Derive Syntax

πŸ”—Field-based derive (generates everything)

#[derive(miniextendr_api::AltrepInteger)]
#[altrep(len = "len_field", elt = "value_field", class = "ClassName")]
pub struct MyType { value_field: i32, len_field: usize }

// Optional: dataptr, serialize, subset via #[altrep(...)] options
#[derive(miniextendr_api::AltrepInteger)]
#[altrep(len = "len", elt_delegate = "data", dataptr, class = "DelegateType")]
pub struct MyType2 { data: Vec<i32>, len: usize }

πŸ”—Manual data traits (generates everything except AltrepLen/Alt*Data)

#[derive(miniextendr_api::AltrepInteger)]
#[altrep(manual, class = "ClassName")]
pub struct MyType { value: i32, len: usize }
// ... then `impl AltrepLen` + `impl AltIntegerData` by hand.

// Same `dataptr` / `serialize` / `subset` options apply:
#[derive(miniextendr_api::AltrepInteger)]
#[altrep(manual, dataptr, serialize, class = "ClassName")]
pub struct MyType2 { /* ... */ }

πŸ”—The impl_alt*_from_data! Macros (escape hatch)

Every derive form above auto-calls the matching macro for you β€” you do not write these calls in normal code. They exist as the escape hatch for #[altrep(no_lowlevel)], when you write the low-level Altrep/AltVec/ Alt* traits by hand and still want the macro for one family, or you’re integrating a type that never goes through a derive at all (built-in types inside miniextendr-api itself work this way).

// Basic (elt only)
impl_altinteger_from_data!(MyType);

// With dataptr
impl_altinteger_from_data!(MyType, dataptr);

// With serialization
impl_altinteger_from_data!(MyType, serialize);

// Multiple options (canonical alphabetical order):
impl_altinteger_from_data!(MyType, dataptr, serialize);
impl_altinteger_from_data!(MyType, subset, serialize);

// Generic implementation: {parameters} Type<...> {where-clause}
impl_altinteger_from_data_generic!(
    {T} GenericInt<T> {T: Copy + Into<i32> + 'static}
);

Each family also exports a matching _generic! sibling.

Available macros, one per family:

  • impl_altinteger_from_data!() - Integer vectors
  • impl_altreal_from_data!() - Real (numeric) vectors
  • impl_altlogical_from_data!() - Logical vectors
  • impl_altraw_from_data!() - Raw (byte) vectors
  • impl_altcomplex_from_data!() - Complex vectors
  • impl_altstring_from_data!() - String (character) vectors
  • impl_altlist_from_data!() - List vectors

πŸ”—Sortedness Values

use miniextendr_api::altrep_data::Sortedness;

fn is_sorted(&self) -> Option<Sortedness> {
    Some(Sortedness::Increasing)       // 1, 2, 3, ...
    Some(Sortedness::Decreasing)       // 3, 2, 1, ...
    Some(Sortedness::IncreasingNAsFirst)  // NA, 1, 2, 3
    Some(Sortedness::DecreasingNAsFirst)  // NA, 3, 2, 1
    Some(Sortedness::Unsorted)         // No pattern
    None  // Unknown (R will check if needed)
}

πŸ”—Logical Values

use miniextendr_api::altrep_data::Logical;

impl AltLogicalData for MyData {
    fn elt(&self, i: usize) -> Logical {
        Logical::True      // TRUE (1)
        Logical::False     // FALSE (0)
        Logical::Na        // NA
    }
}

πŸ”—Common Patterns

πŸ”—Constant Vector

fn elt(&self, _i: usize) -> T { self.constant_value }

πŸ”—Arithmetic Sequence

fn elt(&self, i: usize) -> T { self.start + i * self.step }

πŸ”—External Data

fn elt(&self, i: usize) -> T { self.source[self.offset + i] }

πŸ”—Computed

fn elt(&self, i: usize) -> T { (self.function)(i) }

πŸ”—DATAPTR Decision Tree

Do you have data in memory?
β”œβ”€ Yes (Vec/Box/slice)
β”‚  └─ Provide dataptr() β†’ Fast operations
β”‚
└─ No (computed/lazy)
   └─ Will users need DATAPTR often?
      β”œβ”€ Yes β†’ Implement with cache
      └─ No β†’ Skip dataptr()

πŸ”—Debugging Checklist

  • Function is pub?
  • Data struct has a per-family derive (#[derive(AltrepInteger)] etc.), with #[altrep(manual)] added if you’re on the manual data-traits pattern?
  • #[altrep(class = "...")] attribute present?
  • For the manual pattern: impl AltrepLen provided?
  • For the manual pattern: impl Alt*Data provided?
  • For the no_lowlevel escape hatch only: called the matching impl_alt*_from_data! macro yourself?
  • Ran devtools::document()?
  • Reinstalled package?

πŸ”—Performance Checklist

  • Provide no_na() if no NAs β†’ Faster operations
  • Provide is_sorted() if sorted β†’ Faster unique/sort
  • Provide sum()/min()/max() if O(1) possible β†’ Much faster
  • Provide get_region() if faster than looping β†’ Faster bulk access (contract + patterns)
  • Provide extract_subset() if O(1) subset β†’ Faster subsetting
  • Skip dataptr() if lazy β†’ Save memory
  • Implement dataptr() if in-memory β†’ Faster operations

πŸ”—Safety Checklist

  • ALTREP derive (#[derive(AltrepInteger)] etc., or the generic #[derive(Altrep)]) on data struct
  • No raw pointers outliving their source
  • RefCell (not Mutex) for interior mutability
  • Returned pointers valid for object lifetime
  • SEXPs in lists are protected
  • Thread safety: no async/threading in callbacks

πŸ”—Receiving ALTREP from R

R frequently passes ALTREP vectors to Rust (e.g., 1:10, seq_len(N)).

Rust parameterALTREP handlingUse when
Vec<i32>, &[f64], etc.Auto-materializedYou need the data
SEXPAuto-materializedYou need the handle, not data
AltrepSexpNot materialized, !SendYou need ALTREP metadata
extern "C-unwind" raw SEXPNo conversion at allYou inspect ALTREP state
// Recommended: typed parameter handles everything
#[miniextendr]
pub fn sum_ints(x: Vec<i32>) -> i64 {
    x.iter().map(|&v| v as i64).sum()
}
// sum_ints(1:1000000)  ← works, ALTREP auto-materialized

// Explicit ALTREP handling
#[miniextendr]
pub fn altrep_len(x: AltrepSexp) -> usize { x.len() }
// altrep_len(1:10)      ← works
// altrep_len(c(1L, 2L)) ← error: not ALTREP

Full guide: Receiving ALTREP from R

πŸ”—Further Reading