Repository: extendr/extendr
Pull request #250 (2021-08-06 17:08:36)

Full Altrep Support
This PR adds an "Altrep" wrapper and the beginnings of the means to implement new Altrep classes.


clauswilke: Super, thanks! Could you add some comments that explain what the various API functions do?
andy-thomason: I'm not sure yet how we will define new Altrep classes.

Given a trait like the ones described, we should be able to make a "universal adapter" from the Altrep
callbacks to our trait methods.

To test, I'll implement `From<Range<i32>>` as an altrep class.

andy-thomason: I'm probably going to narrow the definition of Altrep to make it easier to use.

Altrep objects have

* A class object (a raw object wrapping the vtable)
* A state object
* An optional materialized vector 

If we always use a struct for the state object (ie. Extptr) we can implement
the altrep functions as a methods of the state.

In an ideal world, we would just need to implement the "elt" method for an
altrep object and leave all the rest to Rust.

The state object should be a regular Rust object rather than an R object.

```rust
    struct MyCompactIntRange {
        start: i32,
        end: i32,
        step: i32,
    }

    impl AltIntegerImpl for MyCompactIntRange {
        fn elt(&self, index: usize) -> i32 {
            self.start + self.step * index
        }
    }
```

yutannihilation: `ALTREP_LENGTH` might have gone in the recent R API change.

``` 
   error[E0425]: cannot find function, tuple struct or tuple variant `ALTREP_LENGTH` in this scope
    --> extendr-api/src/wrapper/altrep.rs:83:27
     |
  83 |                 let len = ALTREP_LENGTH(x);
     |                           ^^^^^^^^^^^^^ not found in this scope
  
  error: aborting due to previous error
```
andy-thomason: @yutannihilation did we establish a mechanism for using #[cfg] with libR-sys?

If so, I can screen out some of the newer features.

The `ALTREP_` functions are good shortcuts to calling altrep dispatches.

yutannihilation: > did we establish a mechanism for using #[cfg] with libR-sys?

(Sorry, I forgot to answer...) Yes, for internal usages within extendr crates, we can set cfg in build.rs.

https://github.com/extendr/extendr/blob/6cf4c1b457d01ce22f2f7fe875a4974e799122eb/extendr-api/build.rs#L10-L17

> If so, I can screen out some of the newer features.

In case a feature is not available in the older versions of R, it sounds the right thing to do. But, this time, the case is that it's not available on the newer version, which means we'll need to remove it in future when the current versions are out of the range of our minimum supported version. So, I'm wondering if it's worth the effort...
clauswilke: > But, this time, the case is that it's not available on the newer version, which means we'll need to remove it in future when the current versions are out of the range of our minimum supported version. So, I'm wondering if it's worth the effort...

Yeah, I had the same thoughts. If it's a feature that is going away we should just work without for all R versions. Or, if critical, reach out to the R core developers and ask them to not remove the feature.
yutannihilation: For `ALTREP_LENGTH()`, it seems we can use `XLENGTH_EX()` instead.

https://github.com/wch/r-source/blob/ea1c48271846cd02ad5f93b5562152ad82778d8d/src/include/Rinlinedfuns.h#L202-L205
yutannihilation: Or just `Rf_xlength()`? Sorry, I'm not familiar with the things around here...
andy-thomason: Claus is right that we can't rely on the functions now in `defn.h`

The sum, min and max functions should work for code, so we can always
call them using `call!`

I still have to implement defaults duplicate and coerce, but then we can merge.

I am planning to finish off the last few SXP types as part of the grant contract.

Patch:
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index e4a1df000..56668b809 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -64,6 +64,8 @@ pub enum Error {
     ExpectedMatrix(Robj),
     ExpectedMatrix3D(Robj),
     ExpectedNumeric(Robj),
+    ExpectedAltrep(Robj),
+
     OutOfRange(Robj),
     MustNotBeNA(Robj),
     ExpectedNonZeroLength(Robj),
@@ -127,6 +129,7 @@ impl std::fmt::Display for Error {
             Error::ExpectedMatrix(robj) => write!(f, "Expected Matrix, got {:?}", robj.rtype()),
             Error::ExpectedMatrix3D(robj) => write!(f, "Expected Matrix3D, got {:?}", robj.rtype()),
             Error::ExpectedNumeric(robj) => write!(f, "Expected Numeric, got {:?}", robj.rtype()),
+            Error::ExpectedAltrep(robj) => write!(f, "Expected Altrep, got {:?}", robj.rtype()),
             Error::OutOfRange(_robj) => write!(f, "Out of range."),
             Error::MustNotBeNA(_robj) => write!(f, "Must not be NA."),
             Error::ExpectedNonZeroLength(_robj) => write!(f, "Expected non zero length"),
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 8524d619d..c147fc32d 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -266,6 +266,8 @@ pub use robj_ndarray::*;
 pub use extendr_macros::*;
 //////////////////////////////////////////////////
 
+pub struct Cplx(pub f64, pub f64);
+
 /// TRUE value eg. `r!(TRUE)`
 pub const TRUE: Bool = Bool(1);
 
@@ -422,6 +424,71 @@ pub enum RType {
     Unknown,
 }
 
+/// Convert extendr's RType to R's SEXPTYPE.
+/// Panics if the type is Unknown.
+pub fn rtype_to_sxp(rtype: RType) -> i32 {
+    use RType::*;
+    (match rtype {
+        Null => NILSXP,
+        Symbol => SYMSXP,
+        Pairlist => LISTSXP,
+        Function => CLOSXP,
+        Environment => ENVSXP,
+        Promise => PROMSXP,
+        Language => LANGSXP,
+        Special => SPECIALSXP,
+        Builtin => BUILTINSXP,
+        Character => CHARSXP,
+        Logical => LGLSXP,
+        Integer => INTSXP,
+        Real => REALSXP,
+        Complex => CPLXSXP,
+        String => STRSXP,
+        Dot => DOTSXP,
+        Any => ANYSXP,
+        List => VECSXP,
+        Expression => EXPRSXP,
+        Bytecode => BCODESXP,
+        ExternalPtr => EXTPTRSXP,
+        WeakRef => WEAKREFSXP,
+        Raw => RAWSXP,
+        S4 => S4SXP,
+        Unknown => panic!("attempt to use Unknown RType"),
+    }) as i32
+}
+
+/// Convert R's SEXPTYPE to extendr's RType.
+pub fn sxp_to_rtype(sxptype: i32) -> RType {
+    use RType::*;
+    match sxptype as u32 {
+        NILSXP => Null,
+        SYMSXP => Symbol,
+        LISTSXP => Pairlist,
+        CLOSXP => Function,
+        ENVSXP => Environment,
+        PROMSXP => Promise,
+        LANGSXP => Language,
+        SPECIALSXP => Special,
+        BUILTINSXP => Builtin,
+        CHARSXP => Character,
+        LGLSXP => Logical,
+        INTSXP => Integer,
+        REALSXP => Real,
+        CPLXSXP => Complex,
+        STRSXP => String,
+        DOTSXP => Dot,
+        ANYSXP => Any,
+        VECSXP => List,
+        EXPRSXP => Expression,
+        BCODESXP => Bytecode,
+        EXTPTRSXP => ExternalPtr,
+        WEAKREFSXP => WeakRef,
+        RAWSXP => Raw,
+        S4SXP => S4,
+        _ => Unknown,
+    }
+}
+
 #[doc(hidden)]
 pub fn print_r_output<T: Into<Vec<u8>>>(s: T) {
     let cs = CString::new(s).expect("NulError");
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 5ea5e58c0..310918f29 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -26,14 +26,21 @@ pub use super::wrapper::symbol::{
     spec_symbol, triple_colon_symbol, tsp_symbol, unbound_value,
 };
 
+// Exported macros have crate scope.
 pub use crate::{append, append_lang, append_with_name, args, lang, make_lang};
+
+// Exported macros have crate scope.
 pub use crate::{
     data_frame, factor, global, list, r, reprint, reprintln, rprint, rprintln, sym, test, var,
 };
 
 pub use super::logical::Bool;
+pub use super::Cplx;
 
-pub use super::wrapper::{RArray, RColumn, RMatrix, RMatrix3D};
+pub use super::wrapper::{
+    AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
+    AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D,
+};
 
 pub use super::robj::{IntoRobj, Robj, RobjItertools};
 
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 9441a176b..f04b858ab 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -154,17 +154,6 @@ impl Robj {
         }
     }
 
-    /// Get a copy of the underlying SEXP for mutable types.
-    /// This is valid only for owned objects as we are not
-    /// permitted to modify parameters or system objects.
-    #[doc(hidden)]
-    pub unsafe fn get_mut(&mut self) -> Option<SEXP> {
-        match self {
-            Robj::Owned(sexp) => Some(*sexp),
-            Robj::Sys(_) => None,
-        }
-    }
-
     #[doc(hidden)]
     /// Get the XXXSXP type of the object.
     pub fn sexptype(&self) -> u32 {
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 39041e55f..1285118d6 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -421,4 +421,34 @@ impl Robj {
     pub fn namespace_env_spec(&self) -> Robj {
         unsafe { new_owned(R_NamespaceEnvSpec(self.get())) }
     }
+
+    /// Returns `true` if this is an ALTREP object.
+    pub fn is_altrep(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 }
+    }
+
+    /// Returns `true` if this is an integer ALTREP object.
+    pub fn is_altinteger(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == INTSXP as i32 }
+    }
+
+    /// Returns `true` if this is an real ALTREP object.
+    pub fn is_altreal(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == REALSXP as i32 }
+    }
+
+    /// Returns `true` if this is an logical ALTREP object.
+    pub fn is_altlogical(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == LGLSXP as i32 }
+    }
+
+    /// Returns `true` if this is a raw ALTREP object.
+    pub fn is_altraw(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == RAWSXP as i32 }
+    }
+
+    /// Returns `true` if this is an integer ALTREP object.
+    pub fn is_altstring(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == STRSXP as i32 }
+    }
 }
diff --git a/extendr-api/src/robj/try_from_robj.rs b/extendr-api/src/robj/try_from_robj.rs
index 37a90b960..4f0ae8248 100644
--- a/extendr-api/src/robj/try_from_robj.rs
+++ b/extendr-api/src/robj/try_from_robj.rs
@@ -285,8 +285,7 @@ impl TryFrom<Robj> for &[i32] {
     /// Convert an INTSXP object into a slice of i32 (integer).
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
-        robj.as_typed_slice()
-            .ok_or_else(|| Error::ExpectedInteger(robj))
+        robj.as_typed_slice().ok_or(Error::ExpectedInteger(robj))
     }
 }
 
@@ -296,8 +295,7 @@ impl TryFrom<Robj> for &[Bool] {
     /// Convert a LGLSXP object into a slice of Bool (tri-state booleans).
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
-        robj.as_typed_slice()
-            .ok_or_else(|| Error::ExpectedLogical(robj))
+        robj.as_typed_slice().ok_or(Error::ExpectedLogical(robj))
     }
 }
 
@@ -306,8 +304,7 @@ impl TryFrom<Robj> for &[u8] {
 
     /// Convert a RAWSXP object into a slice of bytes.
     fn try_from(robj: Robj) -> Result<Self> {
-        robj.as_typed_slice()
-            .ok_or_else(|| Error::ExpectedRaw(robj))
+        robj.as_typed_slice().ok_or(Error::ExpectedRaw(robj))
     }
 }
 
@@ -317,8 +314,7 @@ impl TryFrom<Robj> for &[f64] {
     /// Convert a REALSXP object into a slice of f64 (double precision floating point).
     /// Use `value.is_na()` to detect NA values.
     fn try_from(robj: Robj) -> Result<Self> {
-        robj.as_typed_slice()
-            .ok_or_else(|| Error::ExpectedReal(robj))
+        robj.as_typed_slice().ok_or(Error::ExpectedReal(robj))
     }
 }
 
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
new file mode 100644
index 000000000..c52dcdff8
--- /dev/null
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -0,0 +1,976 @@
+use super::*;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Altrep {
+    pub(crate) robj: Robj,
+}
+
+/// Rust trait for implementing ALTREP.
+/// Implement one or more of these methods to generate an Altrep class.
+/// This is likely to be unstable for a while.
+pub trait AltrepImpl: Clone + std::fmt::Debug {
+    /// Constructor that is called when loading an Altrep object from a file.
+    fn unserialize_ex(
+        class: Robj,
+        state: Robj,
+        attributes: Robj,
+        obj_flags: i32,
+        levels: i32,
+    ) -> Robj {
+        let res = Self::unserialize(class, state);
+        if !res.is_null() {
+            unsafe {
+                let val = res.get();
+                SET_ATTRIB(val, attributes.get());
+                SET_OBJECT(val, obj_flags);
+                SETLEVELS(val, levels);
+            }
+        }
+        res
+    }
+
+    /// Simplified constructor that is called when loading an Altrep object from a file.
+    fn unserialize(_class: Robj, _state: Robj) -> Robj {
+        // We plan to hadle this via Serde by November.
+        ().into()
+    }
+
+    /// Fetch the state of this object when writing to a file.
+    fn serialized_state(_x: SEXP) -> Robj {
+        // We plan to hadle this via Serde by November.
+        ().into()
+    }
+
+    /// Duplicate this object, possibly duplicating attributes.
+    /// Currently this manifests the array but preserves the original object.
+    fn duplicate_ex(x: SEXP, deep: bool) -> Robj {
+        Self::duplicate(x, deep)
+    }
+
+    /// Duplicate this object. Called by Rf_duplicate.
+    /// Currently this manifests the array but preserves the original object.
+    fn duplicate(x: SEXP, _deep: bool) -> Robj {
+        unsafe { new_owned(manifest(x)) }
+    }
+
+    /// Coerce this object into some other type, if possible.
+    fn coerce(_x: SEXP, _ty: RType) -> Robj {
+        ().into()
+    }
+
+    /// Print the text for .Internal(inspect(obj))
+    fn inspect(
+        &self,
+        _pre: i32,
+        _deep: bool,
+        _pvec: i32, // _inspect_subtree: fn(robj: Robj, pre: i32, deep: i32, pvec: i32),
+    ) -> bool {
+        rprintln!("{:?}", self);
+        true
+    }
+
+    /// Get the virtual length of the vector.
+    /// For example for a compact range, return end - start + 1.
+    fn length(&self) -> usize;
+
+    /// Get the data pointer for this vector, possibly expanding the
+    /// compact representation into a full R vector.
+    fn dataptr(x: SEXP, _writeable: bool) -> *mut u8 {
+        unsafe {
+            let data2 = R_altrep_data2(x);
+            if data2 == R_NilValue || TYPEOF(data2) != TYPEOF(x) {
+                let data2 = manifest(x);
+                R_set_altrep_data2(x, data2);
+                DATAPTR(data2) as *mut u8
+            } else {
+                DATAPTR(data2) as *mut u8
+            }
+        }
+    }
+
+    /// Get the data pointer for this vector, returning NULL
+    /// if the object is unmaterialized.
+    fn dataptr_or_null(x: SEXP) -> *const u8 {
+        unsafe {
+            let data2 = R_altrep_data2(x);
+            if data2 == R_NilValue || TYPEOF(data2) != TYPEOF(x) {
+                std::ptr::null()
+            } else {
+                DATAPTR(data2) as *const u8
+            }
+        }
+    }
+
+    /// Implement subsetting (eg. x[10:19]) for this Altrep vector.
+    fn extract_subset(_x: Robj, _indx: Robj, _call: Robj) -> Robj {
+        // only available in later versions of R.
+        // x.extract_subset(indx, call)
+        Robj::from(())
+    }
+}
+
+// Manifest a vector by storing the "elt" values to memory.
+// Return the new vector.
+fn manifest(x: SEXP) -> SEXP {
+    unsafe {
+        Rf_protect(x);
+        let len = XLENGTH_EX(x);
+        let data2 = Rf_allocVector(TYPEOF(x) as u32, len as R_xlen_t);
+        Rf_protect(data2);
+        match TYPEOF(x) as u32 {
+            INTSXP => {
+                INTEGER_GET_REGION(x, 0, len as R_xlen_t, INTEGER(data2));
+            }
+            LGLSXP => {
+                LOGICAL_GET_REGION(x, 0, len as R_xlen_t, LOGICAL(data2));
+            }
+            REALSXP => {
+                REAL_GET_REGION(x, 0, len as R_xlen_t, REAL(data2));
+            }
+            RAWSXP => {
+                RAW_GET_REGION(x, 0, len as R_xlen_t, RAW(data2));
+            }
+            CPLXSXP => {
+                COMPLEX_GET_REGION(x, 0, len as R_xlen_t, COMPLEX(data2));
+            }
+            _ => panic!("unsupported ALTREP type."),
+        };
+        Rf_unprotect(2);
+        data2
+    }
+}
+
+pub trait AltIntegerImpl: AltrepImpl {
+    fn tot_min_max_nas(&self) -> (i64, i32, i32, usize, usize) {
+        let len = self.length();
+        let mut tot = 0;
+        let mut nas = 0;
+        let mut min = i32::MAX;
+        let mut max = i32::MIN;
+        for i in 0..len {
+            let val = self.elt(i);
+            if !val.is_na() {
+                tot += val as i64;
+                min = min.min(val);
+                max = max.max(val);
+                nas += 1;
+            }
+        }
+        (tot, min, max, len - nas, len)
+    }
+
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> i32;
+
+    /// Get a multiple elements from this vector.
+    fn get_region(&self, index: usize, data: &mut [i32]) -> usize {
+        let len = self.length();
+        if index > len {
+            0
+        } else {
+            let num_elems = data.len().min(len - index);
+            let dest = &mut data[0..num_elems];
+            for (i, d) in dest.iter_mut().enumerate() {
+                *d = self.elt(i);
+            }
+            num_elems
+        }
+    }
+
+    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
+    fn is_sorted(&self) -> Bool {
+        NA_LOGICAL
+    }
+
+    /// Return true if this vector does not contain NAs.
+    fn no_na(&self) -> bool {
+        false
+    }
+
+    /// Return the sum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn sum(&self, remove_nas: bool) -> Robj {
+        let (tot, _min, _max, nas, _len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 {
+            NA_INTEGER.into()
+        } else {
+            tot.into()
+        }
+    }
+
+    /// Return the minimum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn min(&self, remove_nas: bool) -> Robj {
+        let (_tot, min, _max, nas, len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 || remove_nas && nas == len {
+            NA_INTEGER.into()
+        } else {
+            min.into()
+        }
+    }
+
+    /// Return the maximum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn max(&self, remove_nas: bool) -> Robj {
+        let (_tot, _min, max, nas, len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 || remove_nas && nas == len {
+            NA_INTEGER.into()
+        } else {
+            max.into()
+        }
+    }
+}
+
+pub trait AltRealImpl: AltrepImpl {
+    fn tot_min_max_nas(&self) -> (f64, f64, f64, usize, usize) {
+        let len = self.length();
+        let mut tot = 0.0;
+        let mut nas = 0;
+        let mut min = f64::MAX;
+        let mut max = f64::MIN;
+        for i in 0..len {
+            let val = self.elt(i);
+            if !val.is_na() {
+                tot += val;
+                min = min.min(val);
+                max = max.max(val);
+                nas += 1;
+            }
+        }
+        (tot, min, max, len - nas, len)
+    }
+
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> f64;
+
+    /// Get a multiple elements from this vector.
+    fn get_region(&self, index: usize, data: &mut [f64]) -> usize {
+        let len = self.length();
+        if index > len {
+            0
+        } else {
+            let num_elems = data.len().min(len - index);
+            let dest = &mut data[0..num_elems];
+            for (i, d) in dest.iter_mut().enumerate() {
+                *d = self.elt(i);
+            }
+            num_elems
+        }
+    }
+
+    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
+    fn is_sorted(&self) -> Bool {
+        NA_LOGICAL
+    }
+
+    /// Return true if this vector does not contain NAs.
+    fn no_na(&self) -> bool {
+        false
+    }
+
+    /// Return the sum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn sum(&self, remove_nas: bool) -> Robj {
+        let (tot, _min, _max, nas, _len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 {
+            NA_REAL.into()
+        } else {
+            tot.into()
+        }
+    }
+
+    /// Return the minimum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn min(&self, remove_nas: bool) -> Robj {
+        let (_tot, min, _max, nas, len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 || remove_nas && nas == len {
+            NA_REAL.into()
+        } else {
+            min.into()
+        }
+    }
+
+    /// Return the maximum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn max(&self, remove_nas: bool) -> Robj {
+        let (_tot, _min, max, nas, len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 || remove_nas && nas == len {
+            NA_REAL.into()
+        } else {
+            max.into()
+        }
+    }
+}
+
+pub trait AltLogicalImpl: AltrepImpl {
+    fn tot_min_max_nas(&self) -> (i64, i32, i32, usize, usize) {
+        let len = self.length();
+        let mut tot = 0;
+        let mut nas = 0;
+        for i in 0..len {
+            let val = self.elt(i);
+            if !val.is_na() {
+                tot += val.0 as i64;
+                nas += 1;
+            }
+        }
+        (tot, 0, 0, len - nas, len)
+    }
+
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> Bool;
+
+    /// Get a multiple elements from this vector.
+    fn get_region(&self, index: usize, data: &mut [Bool]) -> usize {
+        let len = self.length();
+        if index > len {
+            0
+        } else {
+            let num_elems = data.len().min(len - index);
+            let dest = &mut data[0..num_elems];
+            for (i, d) in dest.iter_mut().enumerate() {
+                *d = self.elt(i);
+            }
+            num_elems
+        }
+    }
+
+    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
+    fn is_sorted(&self) -> Bool {
+        NA_LOGICAL
+    }
+
+    /// Return true if this vector does not contain NAs.
+    fn no_na(&self) -> bool {
+        false
+    }
+
+    /// Return the sum of the elements in this vector.
+    /// If remove_nas is true, skip and NA values.
+    fn sum(&self, remove_nas: bool) -> Robj {
+        let (tot, _min, _max, nas, len) = self.tot_min_max_nas();
+        if !remove_nas && nas != 0 || remove_nas && nas == len {
+            NA_LOGICAL.into()
+        } else {
+            tot.into()
+        }
+    }
+}
+
+pub trait AltRawImpl: AltrepImpl {
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> u8;
+
+    /// Get a multiple elements from this vector.
+    fn get_region(&self, index: usize, data: &mut [u8]) -> usize {
+        let len = self.length();
+        if index > len {
+            0
+        } else {
+            let num_elems = data.len().min(len - index);
+            let dest = &mut data[0..num_elems];
+            for (i, d) in dest.iter_mut().enumerate() {
+                *d = self.elt(i);
+            }
+            num_elems
+        }
+    }
+}
+
+pub trait AltComplexImpl: AltrepImpl {
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> Cplx;
+
+    /// Get a multiple elements from this vector.
+    fn get_region(&self, index: usize, data: &mut [Cplx]) -> usize {
+        let len = self.length();
+        if index > len {
+            0
+        } else {
+            let num_elems = data.len().min(len - index);
+            let dest = &mut data[0..num_elems];
+            for (i, d) in dest.iter_mut().enumerate() {
+                *d = self.elt(i);
+            }
+            num_elems
+        }
+    }
+}
+
+pub trait AltStringImpl {
+    /// Get a single element from this vector.
+    fn elt(&self, _index: usize) -> String;
+
+    /// Set a single element in this vector.
+    fn set_elt(&mut self, _index: usize, _value: Robj) {}
+
+    /// Return TRUE if this vector is sorted, FALSE if not and NA_LOGICAL if unknown.
+    fn is_sorted(&self) -> Bool {
+        NA_LOGICAL
+    }
+
+    /// Return true if this vector does not contain NAs.
+    fn no_na(&self) -> bool {
+        false
+    }
+}
+
+impl Altrep {
+    /// Safely implement R_altrep_data1, R_altrep_data2.
+    /// When implementing Altrep classes, this gets the metadata.
+    pub fn data(&self) -> (Robj, Robj) {
+        unsafe {
+            (
+                new_owned(R_altrep_data1(self.robj.get())),
+                new_owned(R_altrep_data1(self.robj.get())),
+            )
+        }
+    }
+
+    /// Safely (relatively!) implement R_set_altrep_data1, R_set_altrep_data2.
+    /// When implementing Altrep classes, this sets the metadata.
+    pub fn set_data(&mut self, values: (Robj, Robj)) {
+        unsafe {
+            R_set_altrep_data1(self.robj.get(), values.0.get());
+            R_set_altrep_data2(self.robj.get(), values.1.get());
+        }
+    }
+
+    /// Safely implement ALTREP_CLASS.
+    pub fn class(&self) -> Robj {
+        unsafe { new_owned(ALTREP_CLASS(self.robj.get())) }
+    }
+
+    pub fn from_state_and_class<StateType: 'static>(
+        state: StateType,
+        class: Robj,
+        mutable: bool,
+    ) -> Altrep {
+        single_threaded(|| unsafe {
+            use std::os::raw::c_void;
+
+            unsafe extern "C" fn finalizer<StateType: 'static>(x: SEXP) {
+                let state = Altrep::get_state_mut::<StateType>(x);
+                let ptr = state as *mut StateType;
+                Box::from_raw(ptr);
+            }
+
+            let ptr: *mut StateType = Box::into_raw(Box::new(state));
+            let tag = r!(());
+            let prot = r!(());
+            let state = R_MakeExternalPtr(ptr as *mut c_void, tag.get(), prot.get());
+            R_RegisterCFinalizer(state, Some(finalizer::<StateType>));
+
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+            let sexp = R_new_altrep(class_ptr, state, R_NilValue);
+
+            if !mutable {
+                MARK_NOT_MUTABLE(sexp);
+            }
+
+            Altrep {
+                robj: new_owned(sexp),
+            }
+        })
+    }
+
+    #[allow(dead_code)]
+    pub(crate) fn get_state<StateType>(x: SEXP) -> &'static StateType {
+        unsafe {
+            let state_ptr = R_ExternalPtrAddr(R_altrep_data1(x));
+            &*(state_ptr as *const StateType)
+        }
+    }
+
+    #[allow(dead_code)]
+    pub(crate) fn get_state_mut<StateType>(x: SEXP) -> &'static mut StateType {
+        unsafe {
+            let state_ptr = R_ExternalPtrAddr(R_altrep_data1(x));
+            &mut *(state_ptr as *mut StateType)
+        }
+    }
+
+    fn altrep_class<StateType: AltrepImpl + 'static>(ty: RType, name: &str, base: &str) -> Robj {
+        #![allow(non_snake_case)]
+        #![allow(unused_variables)]
+        use std::os::raw::c_int;
+        use std::os::raw::c_void;
+
+        unsafe extern "C" fn altrep_UnserializeEX<StateType: AltrepImpl>(
+            class: SEXP,
+            state: SEXP,
+            attr: SEXP,
+            objf: c_int,
+            levs: c_int,
+        ) -> SEXP {
+            <StateType>::unserialize_ex(
+                new_owned(class),
+                new_owned(state),
+                new_owned(attr),
+                objf as i32,
+                levs as i32,
+            )
+            .get()
+        }
+
+        unsafe extern "C" fn altrep_Unserialize<StateType: AltrepImpl + 'static>(
+            class: SEXP,
+            state: SEXP,
+        ) -> SEXP {
+            <StateType>::unserialize(new_owned(class), new_owned(state)).get()
+        }
+
+        unsafe extern "C" fn altrep_Serialized_state<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+        ) -> SEXP {
+            <StateType>::serialized_state(x).get()
+        }
+
+        unsafe extern "C" fn altrep_Coerce<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            ty: c_int,
+        ) -> SEXP {
+            <StateType>::coerce(x, sxp_to_rtype(ty)).get()
+        }
+
+        unsafe extern "C" fn altrep_Duplicate<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            deep: Rboolean,
+        ) -> SEXP {
+            <StateType>::duplicate(x, deep == 1).get()
+        }
+
+        unsafe extern "C" fn altrep_DuplicateEX<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            deep: Rboolean,
+        ) -> SEXP {
+            <StateType>::duplicate_ex(x, deep == 1).get()
+        }
+
+        unsafe extern "C" fn altrep_Inspect<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            pre: c_int,
+            deep: c_int,
+            pvec: c_int,
+            func: Option<unsafe extern "C" fn(arg1: SEXP, arg2: c_int, arg3: c_int, arg4: c_int)>,
+        ) -> Rboolean {
+            if Altrep::get_state::<StateType>(x).inspect(pre, deep == 1, pvec) {
+                1
+            } else {
+                0
+            }
+        }
+
+        unsafe extern "C" fn altrep_Length<StateType: AltrepImpl + 'static>(x: SEXP) -> R_xlen_t {
+            Altrep::get_state::<StateType>(x).length() as R_xlen_t
+        }
+
+        unsafe extern "C" fn altvec_Dataptr<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            writeable: Rboolean,
+        ) -> *mut c_void {
+            <StateType>::dataptr(x, writeable != 0) as *mut c_void
+        }
+
+        unsafe extern "C" fn altvec_Dataptr_or_null<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+        ) -> *const c_void {
+            <StateType>::dataptr_or_null(x) as *mut c_void
+        }
+
+        unsafe extern "C" fn altvec_Extract_subset<StateType: AltrepImpl + 'static>(
+            x: SEXP,
+            indx: SEXP,
+            call: SEXP,
+        ) -> SEXP {
+            <StateType>::extract_subset(new_owned(x), new_owned(indx), new_owned(call)).get()
+        }
+
+        unsafe {
+            let csname = std::ffi::CString::new(name).unwrap();
+            let csbase = std::ffi::CString::new(base).unwrap();
+
+            let class_ptr = match ty {
+                RType::Integer => {
+                    R_make_altinteger_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                RType::Real => {
+                    R_make_altreal_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                RType::Logical => {
+                    R_make_altlogical_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                RType::Raw => {
+                    R_make_altraw_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                RType::Complex => {
+                    R_make_altcomplex_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                RType::String => {
+                    R_make_altstring_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
+                _ => panic!("expected Altvec compatible type"),
+            };
+
+            R_set_altrep_UnserializeEX_method(class_ptr, Some(altrep_UnserializeEX::<StateType>));
+            R_set_altrep_Unserialize_method(class_ptr, Some(altrep_Unserialize::<StateType>));
+            R_set_altrep_Serialized_state_method(
+                class_ptr,
+                Some(altrep_Serialized_state::<StateType>),
+            );
+            R_set_altrep_DuplicateEX_method(class_ptr, Some(altrep_DuplicateEX::<StateType>));
+            R_set_altrep_Duplicate_method(class_ptr, Some(altrep_Duplicate::<StateType>));
+            R_set_altrep_Coerce_method(class_ptr, Some(altrep_Coerce::<StateType>));
+            R_set_altrep_Inspect_method(class_ptr, Some(altrep_Inspect::<StateType>));
+            R_set_altrep_Length_method(class_ptr, Some(altrep_Length::<StateType>));
+
+            R_set_altvec_Dataptr_method(class_ptr, Some(altvec_Dataptr::<StateType>));
+            R_set_altvec_Dataptr_or_null_method(
+                class_ptr,
+                Some(altvec_Dataptr_or_null::<StateType>),
+            );
+            R_set_altvec_Extract_subset_method(class_ptr, Some(altvec_Extract_subset::<StateType>));
+
+            new_owned(class_ptr.ptr)
+        }
+    }
+
+    /// Make an integer ALTREP class that can be used to make vectors.
+    pub fn make_altinteger_class<StateType: AltrepImpl + AltIntegerImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+        use std::os::raw::c_int;
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::Integer, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altinteger_Elt<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).elt(i as usize) as c_int
+            }
+
+            unsafe extern "C" fn altinteger_Get_region<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                n: R_xlen_t,
+                buf: *mut c_int,
+            ) -> R_xlen_t {
+                let slice = std::slice::from_raw_parts_mut(buf, n as usize);
+                Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
+            }
+
+            unsafe extern "C" fn altinteger_Is_sorted<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+            }
+
+            unsafe extern "C" fn altinteger_No_NA<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                if Altrep::get_state::<StateType>(x).no_na() {
+                    1
+                } else {
+                    0
+                }
+            }
+
+            unsafe extern "C" fn altinteger_Sum<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).sum(narm == 1).get()
+            }
+
+            unsafe extern "C" fn altinteger_Min<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).min(narm == 1).get()
+            }
+
+            unsafe extern "C" fn altinteger_Max<StateType: AltIntegerImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).max(narm == 1).get()
+            }
+
+            R_set_altinteger_Elt_method(class_ptr, Some(altinteger_Elt::<StateType>));
+            R_set_altinteger_Get_region_method(class_ptr, Some(altinteger_Get_region::<StateType>));
+            R_set_altinteger_Is_sorted_method(class_ptr, Some(altinteger_Is_sorted::<StateType>));
+            R_set_altinteger_No_NA_method(class_ptr, Some(altinteger_No_NA::<StateType>));
+            R_set_altinteger_Sum_method(class_ptr, Some(altinteger_Sum::<StateType>));
+            R_set_altinteger_Min_method(class_ptr, Some(altinteger_Min::<StateType>));
+            R_set_altinteger_Max_method(class_ptr, Some(altinteger_Max::<StateType>));
+
+            class
+        })
+    }
+
+    /// Make a real ALTREP class that can be used to make vectors.
+    pub fn make_altreal_class<StateType: AltrepImpl + AltRealImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+        use std::os::raw::c_int;
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::Real, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altreal_Elt<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> f64 {
+                Altrep::get_state::<StateType>(x).elt(i as usize) as f64
+            }
+
+            unsafe extern "C" fn altreal_Get_region<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                n: R_xlen_t,
+                buf: *mut f64,
+            ) -> R_xlen_t {
+                let slice = std::slice::from_raw_parts_mut(buf, n as usize);
+                Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
+            }
+
+            unsafe extern "C" fn altreal_Is_sorted<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+            }
+
+            unsafe extern "C" fn altreal_No_NA<StateType: AltRealImpl + 'static>(x: SEXP) -> c_int {
+                if Altrep::get_state::<StateType>(x).no_na() {
+                    1
+                } else {
+                    0
+                }
+            }
+
+            unsafe extern "C" fn altreal_Sum<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).sum(narm == 1).get()
+            }
+
+            unsafe extern "C" fn altreal_Min<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).min(narm == 1).get()
+            }
+
+            unsafe extern "C" fn altreal_Max<StateType: AltRealImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).max(narm == 1).get()
+            }
+
+            R_set_altreal_Elt_method(class_ptr, Some(altreal_Elt::<StateType>));
+            R_set_altreal_Get_region_method(class_ptr, Some(altreal_Get_region::<StateType>));
+            R_set_altreal_Is_sorted_method(class_ptr, Some(altreal_Is_sorted::<StateType>));
+            R_set_altreal_No_NA_method(class_ptr, Some(altreal_No_NA::<StateType>));
+            R_set_altreal_Sum_method(class_ptr, Some(altreal_Sum::<StateType>));
+            R_set_altreal_Min_method(class_ptr, Some(altreal_Min::<StateType>));
+            R_set_altreal_Max_method(class_ptr, Some(altreal_Max::<StateType>));
+            class
+        })
+    }
+
+    /// Make a logical ALTREP class that can be used to make vectors.
+    pub fn make_altlogical_class<StateType: AltrepImpl + AltLogicalImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+        use std::os::raw::c_int;
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::Logical, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altlogical_Elt<StateType: AltLogicalImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).elt(i as usize).0 as c_int
+            }
+
+            unsafe extern "C" fn altlogical_Get_region<StateType: AltLogicalImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                n: R_xlen_t,
+                buf: *mut c_int,
+            ) -> R_xlen_t {
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Bool, n as usize);
+                Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
+            }
+
+            unsafe extern "C" fn altlogical_Is_sorted<StateType: AltLogicalImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+            }
+
+            unsafe extern "C" fn altlogical_No_NA<StateType: AltLogicalImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                if Altrep::get_state::<StateType>(x).no_na() {
+                    1
+                } else {
+                    0
+                }
+            }
+
+            unsafe extern "C" fn altlogical_Sum<StateType: AltLogicalImpl + 'static>(
+                x: SEXP,
+                narm: Rboolean,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).sum(narm == 1).get()
+            }
+
+            R_set_altlogical_Elt_method(class_ptr, Some(altlogical_Elt::<StateType>));
+            R_set_altlogical_Get_region_method(class_ptr, Some(altlogical_Get_region::<StateType>));
+            R_set_altlogical_Is_sorted_method(class_ptr, Some(altlogical_Is_sorted::<StateType>));
+            R_set_altlogical_No_NA_method(class_ptr, Some(altlogical_No_NA::<StateType>));
+            R_set_altlogical_Sum_method(class_ptr, Some(altlogical_Sum::<StateType>));
+
+            class
+        })
+    }
+
+    /// Make a raw ALTREP class that can be used to make vectors.
+    pub fn make_altraw_class<StateType: AltrepImpl + AltRawImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::Raw, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altraw_Elt<StateType: AltRawImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> Rbyte {
+                Altrep::get_state::<StateType>(x).elt(i as usize) as Rbyte
+            }
+
+            unsafe extern "C" fn altraw_Get_region<StateType: AltRawImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                n: R_xlen_t,
+                buf: *mut u8,
+            ) -> R_xlen_t {
+                let slice = std::slice::from_raw_parts_mut(buf, n as usize);
+                Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
+            }
+
+            R_set_altraw_Elt_method(class_ptr, Some(altraw_Elt::<StateType>));
+            R_set_altraw_Get_region_method(class_ptr, Some(altraw_Get_region::<StateType>));
+
+            class
+        })
+    }
+
+    /// Make a complex ALTREP class that can be used to make vectors.
+    pub fn make_altcomplex_class<StateType: AltrepImpl + AltComplexImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::Complex, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altcomplex_Elt<StateType: AltComplexImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> Rcomplex {
+                std::mem::transmute(Altrep::get_state::<StateType>(x).elt(i as usize))
+            }
+
+            unsafe extern "C" fn altcomplex_Get_region<StateType: AltComplexImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                n: R_xlen_t,
+                buf: *mut Rcomplex,
+            ) -> R_xlen_t {
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Cplx, n as usize);
+                Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
+            }
+
+            R_set_altcomplex_Elt_method(class_ptr, Some(altcomplex_Elt::<StateType>));
+            R_set_altcomplex_Get_region_method(class_ptr, Some(altcomplex_Get_region::<StateType>));
+
+            class
+        })
+    }
+
+    /// Make a complex ALTREP class that can be used to make vectors.
+    pub fn make_altstring_class<StateType: AltrepImpl + AltStringImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+        use std::os::raw::c_char;
+        use std::os::raw::c_int;
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(RType::String, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altstring_Elt<StateType: AltStringImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> SEXP {
+                let s = Altrep::get_state::<StateType>(x).elt(i as usize);
+                Rf_mkCharLen(s.as_ptr() as *mut c_char, s.len() as c_int)
+            }
+
+            unsafe extern "C" fn altstring_Set_elt<StateType: AltStringImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                v: SEXP,
+            ) {
+                Altrep::get_state_mut::<StateType>(x).set_elt(i as usize, new_owned(v))
+            }
+
+            unsafe extern "C" fn altstring_Is_sorted<StateType: AltStringImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                Altrep::get_state::<StateType>(x).is_sorted().0 as c_int
+            }
+
+            unsafe extern "C" fn altstring_No_NA<StateType: AltStringImpl + 'static>(
+                x: SEXP,
+            ) -> c_int {
+                if Altrep::get_state::<StateType>(x).no_na() {
+                    1
+                } else {
+                    0
+                }
+            }
+
+            R_set_altstring_Elt_method(class_ptr, Some(altstring_Elt::<StateType>));
+            R_set_altstring_Set_elt_method(class_ptr, Some(altstring_Set_elt::<StateType>));
+            R_set_altstring_Is_sorted_method(class_ptr, Some(altstring_Is_sorted::<StateType>));
+            R_set_altstring_No_NA_method(class_ptr, Some(altstring_No_NA::<StateType>));
+
+            class
+        })
+    }
+}
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 2ce4c22fe..01a02a2ef 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -4,6 +4,7 @@
 use crate::*;
 use libR_sys::*;
 
+pub mod altrep;
 pub mod character;
 pub mod environment;
 pub mod expr;
@@ -18,6 +19,10 @@ pub mod promise;
 pub mod raw;
 pub mod symbol;
 
+pub use altrep::{
+    AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
+    AltrepImpl,
+};
 pub use character::Character;
 pub use environment::{EnvIter, Environment};
 pub use expr::Expression;
@@ -100,12 +105,14 @@ macro_rules! make_conversions {
 }
 
 make_conversions!(Pairlist, ExpectedPairlist, is_pairlist, "Not a pairlist");
+
 make_conversions!(
     Function,
     ExpectedFunction,
     is_function,
     "Not a function or primitive."
 );
+
 make_conversions!(Raw, ExpectedRaw, is_raw, "Not a raw object");
 
 make_conversions!(
@@ -149,6 +156,8 @@ make_conversions!(
 
 make_conversions!(Promise, ExpectedPromise, is_promise, "Not a Promise object");
 
+make_conversions!(Altrep, ExpectedAltrep, is_altrep, "Not an Altrep type");
+
 impl Robj {
     /// Convert a symbol object to a Symbol wrapper.
     /// ```


Test patch:
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
new file mode 100644
index 000000000..ab8838722
--- /dev/null
+++ b/extendr-api/tests/altrep_tests.rs
@@ -0,0 +1,193 @@
+use extendr_api::prelude::*;
+
+#[test]
+fn test_altinteger() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct MyCompactIntRange {
+            start: i32,
+            len: i32,
+            step: i32,
+        }
+
+        impl AltrepImpl for MyCompactIntRange {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltIntegerImpl for MyCompactIntRange {
+            fn elt(&self, index: usize) -> i32 {
+                self.start + self.step * index as i32
+            }
+        }
+
+        let mystate = MyCompactIntRange { start: 0, len: 10, step: 1 };
+
+        let class = Altrep::make_altinteger_class::<MyCompactIntRange>("cir", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        // assert_eq!(obj.sum(true), r!(45.0));
+        assert_eq!(obj.as_integer_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+    }
+}
+
+#[test]
+
+fn test_altreal() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct MyCompactRealRange {
+            start: f64,
+            len: usize,
+            step: f64,
+        }
+
+        impl AltrepImpl for MyCompactRealRange {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltRealImpl for MyCompactRealRange {
+            fn elt(&self, index: usize) -> f64 {
+                self.start + self.step * index as f64
+            }
+        }
+
+        let mystate = MyCompactRealRange { start: 0.0, len: 10, step: 1.0 };
+
+        let class = Altrep::make_altreal_class::<MyCompactRealRange>("crr", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        // assert_eq!(obj.sum(true), r!(45.0));
+        assert_eq!(obj.as_real_slice().unwrap(), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
+    }
+}
+
+#[test]
+fn test_altlogical() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct IsEven {
+            len: usize,
+        }
+
+        impl AltrepImpl for IsEven {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltLogicalImpl for IsEven {
+            fn elt(&self, index: usize) -> Bool {
+                (index % 2 == 1).into()
+            }
+        }
+
+        let mystate = IsEven { len: 10 };
+
+        let class = Altrep::make_altlogical_class::<IsEven>("iseven", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        // assert_eq!(obj.sum(true), r!(5.0));
+        assert_eq!(obj.as_logical_slice().unwrap(), [FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE]);
+    }
+}
+
+#[test]
+fn test_altraw() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct MyCompactRawRange {
+            start: i32,
+            len: i32,
+            step: i32,
+        }
+
+        impl AltrepImpl for MyCompactRawRange {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltRawImpl for MyCompactRawRange {
+            fn elt(&self, index: usize) -> u8 {
+                (self.start + self.step * index as i32) as u8
+            }
+        }
+
+        let mystate = MyCompactRawRange { start: 0, len: 10, step: 1 };
+
+        let class = Altrep::make_altraw_class::<MyCompactRawRange>("cir", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        assert_eq!(obj.as_raw_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+    }
+}
+
+#[test]
+fn test_altcomplex() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct MyCompactComplexRange {
+            start: f64,
+            len: usize,
+            step: f64,
+        }
+
+        impl AltrepImpl for MyCompactComplexRange {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltComplexImpl for MyCompactComplexRange {
+            fn elt(&self, index: usize) -> Cplx {
+                Cplx(self.start + self.step * index as f64, self.start + self.step * index as f64)
+            }
+        }
+
+        let mystate = MyCompactComplexRange { start: 0.0, len: 10, step: 1.0 };
+
+        let class = Altrep::make_altcomplex_class::<MyCompactComplexRange>("ccr", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        //assert_eq!(obj.as_complex_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+    }
+}
+
+#[test]
+fn test_altstring() {
+    test! {
+        #[derive(Debug, Clone)]
+        struct StringInts {
+            len: usize
+        }
+
+        impl AltrepImpl for StringInts {
+            fn length(&self) -> usize {
+                self.len as usize
+            }
+        }
+
+        impl AltStringImpl for StringInts {
+            fn elt(&self, index: usize) -> String {
+                format!("{}", index).into()
+            }
+        }
+
+        let mystate = StringInts { len: 10 };
+
+        let class = Altrep::make_altstring_class::<StringInts>("si", "mypkg");
+        let obj = Altrep::from_state_and_class(mystate, class, false);
+
+        assert_eq!(obj.len(), 10);
+        assert_eq!(Robj::from(obj), r!(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]));
+    }
+}