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 Traits (3 steps)

// 1. Define data struct with registration derive
#[derive(miniextendr_api::Altrep)]
#[altrep(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 }
}

// 3. Generate low-level traits
miniextendr_api::impl_altinteger_from_data!(MyData);

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

πŸ”—Optional Methods

FeatureTrait MethodMacro OptionWhen to Use
Bulk accessget_region()-Faster than looping elt()
DataptrAltrepDataptr<T>dataptrData in contiguous memory
SerializationAltrepSerializeserializeSave/load support needed
Subsettingextract_subset()subsetO(1) subset possible
Mutationset_elt()set_eltMutable String/List
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 traits macro syntax

// 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
impl_altinteger_from_data!(MyType, dataptr, serialize, subset, set_elt);

πŸ”—Available Macros

  • 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 #[derive(AltrepInteger)] (or #[derive(Altrep)] for manual pattern)?
  • #[altrep(class = "...")] attribute present?
  • For manual pattern: impl AltrepLen provided?
  • For manual pattern: impl Alt*Data provided?
  • For manual pattern: used correct impl_alt*_from_data! macro?
  • 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
  • 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(Altrep)] or #[derive(AltrepInteger)] etc.) 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