Repository: extendr/extendr
Pull request #260 (2021-08-15 23:37:31)

Formal external pointer support
This PR allows the creation of external pointer objects which own Rust objects.

The "tag" field of the object contains the name of the type as a string so that some
minimal protection against incorrect types is provided.

```
#[extendr]
fn i_take_an_extptr(obj: Extptr<MyStruct>) {
    obj.my_method();
}
```

Extptr uses Deref to make the R object look like the Rust object it wraps.
It would probably be wise to cache the deref:

```
#[extendr]
fn i_take_an_extptr(obj: Extptr<MyStruct>) {
    let myref = &*obj;
    myref.my_method();
    myref.my_method2();
}
```

There is already some informal extptr support used by the impl mechanism
that would work with more random extptr implementations from third parties.

This makes a very easy extptr mechanism that wraps a Rust object and returns
its reference to Rust via R.



Patch:
diff --git a/extendr-api/Cargo.toml b/extendr-api/Cargo.toml
index 7f82383bd..9e5591349 100644
--- a/extendr-api/Cargo.toml
+++ b/extendr-api/Cargo.toml
@@ -19,6 +19,7 @@ extendr-macros = { path = "../extendr-macros", version="0.2.0" }
 extendr-engine = { path = "../extendr-engine", version="0.2.0" }
 ndarray = { version = "0.15.3", optional = true }
 lazy_static = "1.4"
+paste = "1.0.5"
 
 [features]
 default = []
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index 553a9c96e..0fb7b5bd5 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -74,6 +74,7 @@ pub enum Error {
     TypeMismatch(Robj),
     NamespaceNotFound(Robj),
 
+    ExpectedExternalPtrType(Robj, String),
     Other(String),
 }
 
@@ -144,6 +145,9 @@ impl std::fmt::Display for Error {
             Error::TypeMismatch(_robj) => write!(f, "Type mismatch"),
 
             Error::NamespaceNotFound(robj) => write!(f, "Namespace {:?} not found", robj),
+            Error::ExpectedExternalPtrType(_robj, type_name) => {
+                write!(f, "Incorrect external pointer type {}", type_name)
+            }
             Error::Other(str) => write!(f, "{}", str),
         }
     }
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 47518d58c..974eede01 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -51,8 +51,8 @@ pub use super::thread_safety::{
 };
 
 pub use super::wrapper::{
-    EnvIter, Environment, Expression, FromList, Function, Integers, Language, List, ListIter,
-    Nullable, Pairlist, Primitive, Promise, Raw, Rstr, Symbol,
+    Doubles, EnvIter, Environment, Expression, ExternalPtr, FromList, Function, Integers, Language,
+    List, ListIter, Nullable, Pairlist, Primitive, Promise, Raw, Rstr, Symbol,
 };
 
 #[cfg(feature = "ndarray")]
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 4990353a6..6e80c684a 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -14,7 +14,7 @@ use std::os::raw;
 
 use crate::*;
 
-use crate::scalar::Rint;
+use crate::scalar::{Rfloat, Rint};
 use std::collections::HashMap;
 use std::iter::IntoIterator;
 use std::ops::{Range, RangeInclusive};
@@ -710,6 +710,7 @@ make_typed_slice!(Bool, INTEGER, LGLSXP);
 make_typed_slice!(i32, INTEGER, INTSXP);
 make_typed_slice!(Rint, INTEGER, INTSXP);
 make_typed_slice!(f64, REAL, REALSXP);
+make_typed_slice!(Rfloat, REAL, REALSXP);
 make_typed_slice!(u8, RAW, RAWSXP);
 
 /// These are helper functions which give access to common properties of R objects.
@@ -1084,7 +1085,9 @@ impl std::fmt::Debug for Robj {
             DOTSXP => write!(f, "r!(Dot())"),
             ANYSXP => write!(f, "r!(Any())"),
             BCODESXP => write!(f, "r!(Bcode())"),
-            EXTPTRSXP => write!(f, "r!(Extptr())"),
+            EXTPTRSXP => {
+                write!(f, "r!(ExternalPtr())")
+            }
             RAWSXP => {
                 write!(
                     f,
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 10a1f5aa3..572b5b55a 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -60,6 +60,11 @@ impl Robj {
         unsafe { Rf_isS4(self.get()) != 0 }
     }
 
+    /// Return true if this is an expression.
+    pub fn is_external_pointer(&self) -> bool {
+        self.rtype() == RType::ExternalPtr
+    }
+
     /// Get the source ref.
     pub fn get_current_srcref(val: i32) -> Robj {
         unsafe { new_owned(R_GetCurrentSrcref(val as raw::c_int)) }
diff --git a/extendr-api/src/scalar/macros.rs b/extendr-api/src/scalar/macros.rs
new file mode 100644
index 000000000..ecb626093
--- /dev/null
+++ b/extendr-api/src/scalar/macros.rs
@@ -0,0 +1,299 @@
+/// Generates unary operators for scalar types.
+macro_rules! gen_unop {
+    ($type : tt, $opname : ident, $expr: expr, $docstring: expr) => {
+        impl $opname for $type {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [<$opname:lower>](self) -> Self::Output {
+                    if let Some(lhs) = self.into() {
+                        let f = $expr;
+                        if let Some(res) = f(lhs) {
+                            // Note that if res is NA, this will also be NA.
+                            return $type::from(res);
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+
+        impl $opname for &$type {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [< $opname:lower >](self) -> Self::Output {
+                    if let Some(lhs) = (*self).into() {
+                        let f = $expr;
+                        if let Some(res) = f(lhs) {
+                            // Note that if res is NA, this will also be NA.
+                            return $type::from(res);
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+    };
+}
+
+/// Generates binary operators for scalar types.
+// TODO: binary operators for pairs `(Rtype, Type)` and `(Type, Rtype)` using references?
+macro_rules! gen_binop {
+    ($type : tt, $type_prim : tt, $opname : ident, $expr: expr, $docstring: expr) => {
+        impl $opname<$type> for $type {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [< $opname:lower >](self, rhs: $type) -> Self::Output {
+                    if let Some(lhs) = self.clone().into() {
+                        if let Some(rhs) = rhs.into() {
+                            let f = $expr;
+                            if let Some(res) = f(lhs, rhs) {
+                                // Note that if res is NA, this will also be NA.
+                                return $type::from(res);
+                            }
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+
+        impl $opname<$type> for &$type {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [< $opname:lower >](self, rhs: $type) -> Self::Output {
+                    if let Some(lhs) = self.clone().into() {
+                        if let Some(rhs) = rhs.into() {
+                            let f = $expr;
+                            if let Some(res) = f(lhs, rhs) {
+                                // Note that if res is NA, this will also be NA.
+                                return $type::from(res);
+                            }
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+
+        impl $opname<$type_prim> for $type {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [< $opname:lower >](self, rhs: $type_prim) -> Self::Output {
+                    if let Some(lhs) = self.clone().into() {
+                        let f = $expr;
+                        if let Some(res) = f(lhs, rhs) {
+                            // Note that if res is NA, this will also be NA.
+                            return $type::from(res);
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+
+        impl $opname<$type> for $type_prim {
+            type Output = $type;
+
+            paste::paste! {
+                #[doc = $docstring]
+                fn [< $opname:lower >](self, rhs: $type) -> Self::Output {
+                    if let Some(rhs) = rhs.clone().into() {
+                        let f = $expr;
+                        if let Some(res) = f(self, rhs) {
+                            // Note that if res is NA, this will also be NA.
+                            return $type::from(res);
+                        }
+                    }
+                    $type::na()
+                }
+            }
+        }
+    };
+}
+
+/// Generates conversions from primitive to scalar type.
+macro_rules! gen_from_primitive {
+    ($type : tt, $type_prim : tt) => {
+        impl From<$type_prim> for $type {
+            fn from(v: $type_prim) -> Self {
+                Self(v)
+            }
+        }
+
+        impl From<Option<$type_prim>> for $type {
+            fn from(v: Option<$type_prim>) -> Self {
+                if let Some(v) = v {
+                    v.into()
+                } else {
+                    $type::na()
+                }
+            }
+        }
+    };
+}
+
+/// Generates conversions from scalar type.
+macro_rules! gen_from_scalar {
+    ($type : tt, $type_prim : tt) => {
+        impl From<$type> for Option<$type_prim> {
+            fn from(v: $type) -> Self {
+                if v.is_na() {
+                    None
+                } else {
+                    Some(v.0)
+                }
+            }
+        }
+
+        impl From<$type> for Robj {
+            fn from(value: $type) -> Self {
+                Robj::from(value.0)
+            }
+        }
+    };
+}
+
+/// Generates required methods:
+/// 1. static `na()`
+/// 2. instance `inner()`
+macro_rules! gen_impl {
+    ($type : ident, $type_prim : ty, $na_val : expr) => {
+        /// Construct a NA.
+        pub fn na() -> Self {
+            $type($na_val)
+        }
+
+        /// Get underlying value.
+        pub fn inner(&self) -> $type_prim {
+            self.0
+        }
+    };
+}
+
+/// Generates scalar trait implementations:
+/// 1. `Clone`
+/// 2. `Copy`
+/// 3. `IsNA`
+/// 4. `Debug`
+/// 5. `PartialEq`
+macro_rules! gen_trait_impl {
+    ($type : ident, $type_prim : ty, $na_check : expr) => {
+        impl Clone for $type {
+            fn clone(&self) -> Self {
+                Self(self.0)
+            }
+        }
+
+        impl Copy for $type {}
+
+        paste::paste! {
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert!((<" $type ">::na()).is_na());"]
+            #[doc = "}"]
+            #[doc = "```"]
+            impl IsNA for $type {
+                /// Return true is the is a NA value.
+                fn is_na(&self) -> bool {
+                    $na_check(self)
+                }
+            }
+        }
+        impl std::fmt::Debug for $type {
+            /// Debug format.
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                let z: Option<$type_prim> = (*self).into();
+                if let Some(val) = z {
+                    write!(f, "{}", val)
+                } else {
+                    write!(f, "na")
+                }
+            }
+        }
+
+        paste::paste! {
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert!(<" $type ">::default().eq(&<" $type ">::default()));"]
+            #[doc = "    assert!(!<" $type ">::na().eq(&<" $type ">::na()));"]
+            #[doc = "}"]
+            #[doc = "```"]
+            impl PartialEq<$type> for $type {
+                fn eq(&self, other: &$type) -> bool {
+                    !(self.is_na() || other.is_na()) && self.0 == other.0
+                }
+            }
+        }
+
+        paste::paste! {
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert!(<" $type ">::default().eq(&<" $type_prim ">::default()));"]
+            #[doc = "}"]
+            #[doc = "```"]
+            impl PartialEq<$type_prim> for $type {
+                /// NA always fails.
+                fn eq(&self, other: &$type_prim) -> bool {
+                    !self.is_na() && self.0 == *other
+                }
+            }
+        }
+
+        paste::paste! {
+            #[doc = "```"]
+            #[doc = "use extendr_api::prelude::*;"]
+            #[doc = "test! {"]
+            #[doc = "    assert_eq!(<" $type ">::default().0, <" $type_prim ">::default());"]
+            #[doc = "}"]
+            #[doc = "```"]
+            impl std::default::Default for $type {
+                fn default() -> Self {
+                    $type(<$type_prim>::default())
+                }
+            }
+        }
+    };
+}
+
+/// Generates `std::iter::Sum` for scalar types.
+macro_rules! gen_sum_iter {
+    ($type : tt, $zero : expr) => {
+        impl std::iter::Sum for $type {
+            paste::paste! {
+                #[doc = "Yields NA on overflow if NAs present."]
+                #[doc = "```"]
+                #[doc = "use extendr_api::prelude::*;"]
+                #[doc = "use std::iter::Sum;"]
+                #[doc = "test! {"]
+                #[doc = "    let x = (0..100).map(|x| " $type "::default());"]
+                #[doc = "    assert_eq!(<" $type " as Sum>::sum(x), <" $type ">::default());"]
+                #[doc = "}"]
+                #[doc = "```"]
+                fn sum<I: Iterator<Item = $type>>(iter: I) -> $type {
+                    iter.fold($type::from($zero), |a, b| a + b)
+                }
+            }
+        }
+    };
+}
+
+pub(in crate::scalar) use gen_binop;
+pub(in crate::scalar) use gen_from_primitive;
+pub(in crate::scalar) use gen_from_scalar;
+pub(in crate::scalar) use gen_impl;
+pub(in crate::scalar) use gen_sum_iter;
+pub(in crate::scalar) use gen_trait_impl;
+pub(in crate::scalar) use gen_unop;
diff --git a/extendr-api/src/scalar/mod.rs b/extendr-api/src/scalar/mod.rs
index ba5905479..bcc09d164 100644
--- a/extendr-api/src/scalar/mod.rs
+++ b/extendr-api/src/scalar/mod.rs
@@ -1,3 +1,5 @@
+mod macros;
+mod rfloat;
 mod rint;
-
+pub use rfloat::Rfloat;
 pub use rint::Rint;
diff --git a/extendr-api/src/scalar/rfloat.rs b/extendr-api/src/scalar/rfloat.rs
new file mode 100644
index 000000000..e9cb026e1
--- /dev/null
+++ b/extendr-api/src/scalar/rfloat.rs
@@ -0,0 +1,102 @@
+use crate::scalar::macros::*;
+use crate::*;
+use std::convert::TryFrom;
+use std::ops::{Add, Div, Mul, Neg, Sub};
+
+/// Rfloat is a wrapper for f64 in the context of an R's integer vector.
+///
+/// Rfloat has a special NA value, obtained from R headers via R_NaReal.
+///
+/// Rfloat has the same footprint as an f64 value allowing us to use it in zero copy slices.
+pub struct Rfloat(pub f64);
+
+impl Rfloat {
+    gen_impl!(Rfloat, f64, unsafe { libR_sys::R_NaReal });
+
+    pub fn is_nan(&self) -> bool {
+        self.0.is_nan()
+    }
+    pub fn is_sign_positive(&self) -> bool {
+        self.0.is_sign_positive()
+    }
+    pub fn is_sign_negative(&self) -> bool {
+        self.0.is_sign_negative()
+    }
+    pub fn is_infinite(&self) -> bool {
+        self.0.is_infinite()
+    }
+    pub fn is_subnormal(&self) -> bool {
+        self.0.is_subnormal()
+    }
+}
+// `NA_real_` is a `NaN` with specific bit representation.
+// Check that underlying `f64` equals (bitwise) to `NA_real_`.
+gen_trait_impl!(Rfloat, f64, |x: &Rfloat| x.0.to_bits()
+    == unsafe { libR_sys::R_NaReal.to_bits() });
+gen_from_primitive!(Rfloat, f64);
+gen_from_scalar!(Rfloat, f64);
+gen_sum_iter!(Rfloat, 0f64);
+
+// Generate binary ops for +, -, * and /
+gen_binop!(
+    Rfloat,
+    f64,
+    Add,
+    |lhs: f64, rhs: f64| Some(lhs + rhs),
+    "Add two Rfloat values or an option of f64."
+);
+gen_binop!(
+    Rfloat,
+    f64,
+    Sub,
+    |lhs: f64, rhs: f64| Some(lhs - rhs),
+    "Subtract two Rfloat values or an option of f64."
+);
+gen_binop!(
+    Rfloat,
+    f64,
+    Mul,
+    |lhs: f64, rhs: f64| Some(lhs * rhs),
+    "Multiply two Rfloat values or an option of f64."
+);
+gen_binop!(
+    Rfloat,
+    f64,
+    Div,
+    |lhs: f64, rhs: f64| Some(lhs / rhs),
+    "Divide two Rfloat values or an option of f64."
+);
+
+// Generate unary ops for -, !
+gen_unop!(Rfloat, Neg, |lhs: f64| Some(-lhs), "Negate a Rfloat value.");
+
+impl TryFrom<Robj> for Rfloat {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        // Check if the value is a scalar
+        match robj.len() {
+            0 => return Err(Error::ExpectedNonZeroLength(robj)),
+            1 => {}
+            _ => return Err(Error::ExpectedScalar(robj)),
+        };
+
+        // Check if the value is not a missing value.
+        if robj.is_na() {
+            return Ok(Rfloat::na());
+        }
+
+        // This should always work, NA is handled above.
+        if let Some(v) = robj.as_real() {
+            return Ok(Rfloat::from(v));
+        }
+
+        // Any integer (32 bit) can be represented as f64,
+        // this always works.
+        if let Some(v) = robj.as_integer() {
+            return Ok(Rfloat::from(v as f64));
+        }
+
+        Err(Error::ExpectedNumeric(robj))
+    }
+}
diff --git a/extendr-api/src/scalar/rint.rs b/extendr-api/src/scalar/rint.rs
index e538fcf26..7cbc7c2a9 100644
--- a/extendr-api/src/scalar/rint.rs
+++ b/extendr-api/src/scalar/rint.rs
@@ -1,3 +1,4 @@
+use crate::scalar::macros::*;
 use crate::*;
 use std::convert::TryFrom;
 use std::ops::{Add, Div, Mul, Neg, Not, Sub};
@@ -9,227 +10,61 @@ use std::ops::{Add, Div, Mul, Neg, Not, Sub};
 /// The value i32::MIN is used as "NA".
 ///
 /// Rint has the same footprint as an i32 value allowing us to use it in zero copy slices.
-#[derive(PartialEq, Eq)]
 pub struct Rint(pub i32);
 
 impl Rint {
-    /// Construct a NA Rint.
-    pub fn na() -> Self {
-        Rint(i32::MIN)
-    }
-
-    /// Get an integer or i32::MIN for NA.
-    pub fn inner(&self) -> i32 {
-        self.0
-    }
-}
-
-impl Clone for Rint {
-    fn clone(&self) -> Self {
-        Self(self.0)
-    }
-}
-
-impl Copy for Rint {}
-
-impl IsNA for Rint {
-    /// Return true is the is a NA value.
-    fn is_na(&self) -> bool {
-        self.0 == std::i32::MIN
-    }
-}
-
-impl From<i32> for Rint {
-    /// Construct a Rint from an integer.
-    /// i32::MIN gives an NA.
-    fn from(v: i32) -> Self {
-        Self(v)
-    }
-}
-
-impl From<Option<i32>> for Rint {
-    /// Construct an Rint from an optional integer.
-    /// None or Some(i32::MIN) gives an NA.
-    fn from(v: Option<i32>) -> Self {
-        if let Some(v) = v {
-            v.into()
-        } else {
-            Rint::na()
-        }
-    }
-}
-
-impl From<Rint> for Option<i32> {
-    /// Convert an Rint to an optional integer.
-    /// NA gives None.
-    fn from(v: Rint) -> Self {
-        if v.is_na() {
-            None
-        } else {
-            Some(v.0)
-        }
-    }
+    gen_impl!(Rint, i32, i32::MIN);
 }
 
-impl std::fmt::Debug for Rint {
-    /// Debug format an Rint.
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let z: Option<i32> = (*self).into();
-        if let Some(val) = z {
-            write!(f, "{}", val)
-        } else {
-            write!(f, "na")
-        }
-    }
-}
-
-macro_rules! gen_binop {
-    ($opname1 : ident, $opname2: ident, $expr: expr, $docstring: expr) => {
-        impl $opname1<Rint> for Rint {
-            type Output = Rint;
-
-            #[doc = $docstring]
-            fn $opname2(self, rhs: Rint) -> Self::Output {
-                if let Some(lhs) = self.clone().into() {
-                    if let Some(rhs) = rhs.into() {
-                        let f = $expr;
-                        if let Some(res) = f(lhs, rhs) {
-                            // Note that if res = i32::MIN, this will also be NA.
-                            return Rint::from(res);
-                        }
-                    }
-                }
-                Rint::na()
-            }
-        }
-
-        impl $opname1<Rint> for &Rint {
-            type Output = Rint;
-
-            #[doc = $docstring]
-            fn $opname2(self, rhs: Rint) -> Self::Output {
-                if let Some(lhs) = self.clone().into() {
-                    if let Some(rhs) = rhs.into() {
-                        let f = $expr;
-                        if let Some(res) = f(lhs, rhs) {
-                            // Note that if res = i32::MIN, this will also be NA.
-                            return Rint::from(res);
-                        }
-                    }
-                }
-                Rint::na()
-            }
-        }
-
-        impl $opname1<i32> for Rint {
-            type Output = Rint;
-
-            #[doc = $docstring]
-            fn $opname2(self, rhs: i32) -> Self::Output {
-                if let Some(lhs) = self.clone().into() {
-                    let f = $expr;
-                    if let Some(res) = f(lhs, rhs) {
-                        // Note that if res = i32::MIN, this will also be NA.
-                        return Rint::from(res);
-                    }
-                }
-                Rint::na()
-            }
-        }
-    };
-}
-
-macro_rules! gen_unnop {
-    ($opname1 : ident, $opname2: ident, $expr: expr, $docstring: expr) => {
-        impl $opname1 for Rint {
-            type Output = Rint;
-
-            #[doc = $docstring]
-            fn $opname2(self) -> Self::Output {
-                if let Some(lhs) = self.into() {
-                    let f = $expr;
-                    if let Some(res) = f(lhs) {
-                        // Note that if res = i32::MIN, this will also be NA.
-                        return Rint::from(res);
-                    }
-                }
-                Rint::na()
-            }
-        }
-
-        impl $opname1 for &Rint {
-            type Output = Rint;
-
-            #[doc = $docstring]
-            fn $opname2(self) -> Self::Output {
-                if let Some(lhs) = (*self).into() {
-                    let f = $expr;
-                    if let Some(res) = f(lhs) {
-                        // Note that if res = i32::MIN, this will also be NA.
-                        return Rint::from(res);
-                    }
-                }
-                Rint::na()
-            }
-        }
-    };
-}
+gen_trait_impl!(Rint, i32, |x: &Rint| x.0 == i32::MIN);
+gen_from_primitive!(Rint, i32);
+gen_from_scalar!(Rint, i32);
+gen_sum_iter!(Rint, 0i32);
 
 // Generate binary ops for +, -, * and /
 gen_binop!(
+    Rint,
+    i32,
     Add,
-    add,
     |lhs: i32, rhs| lhs.checked_add(rhs),
     "Add two Rint values or an option of i32, overflows to NA."
 );
 gen_binop!(
+    Rint,
+    i32,
     Sub,
-    sub,
     |lhs: i32, rhs| lhs.checked_sub(rhs),
     "Subtract two Rint values or an option of i32, overflows to NA."
 );
 gen_binop!(
+    Rint,
+    i32,
     Mul,
-    mul,
     |lhs: i32, rhs| lhs.checked_mul(rhs),
     "Multiply two Rint values or an option of i32, overflows to NA."
 );
 gen_binop!(
+    Rint,
+    i32,
     Div,
-    div,
     |lhs: i32, rhs| lhs.checked_div(rhs),
     "Divide two Rint values or an option of i32, overflows to NA."
 );
 
 // Generate unary ops for -, !
-gen_unnop!(
+gen_unop!(
+    Rint,
     Neg,
-    neg,
     |lhs: i32| Some(-lhs),
     "Negate a Rint value, overflows to NA."
 );
-gen_unnop!(
+gen_unop!(
+    Rint,
     Not,
-    not,
     |lhs: i32| Some(!lhs),
     "Logical not a Rint value, overflows to NA."
 );
 
-impl std::iter::Sum for Rint {
-    /// Sum an integer iterator over Rint.
-    /// Yields NA on overflow of NAs present.
-    fn sum<I: Iterator<Item = Rint>>(iter: I) -> Rint {
-        iter.fold(Rint::from(0), |a, b| a + b)
-    }
-}
-
-impl PartialEq<i32> for Rint {
-    /// Compare a Rint with an integer. NA always fails.
-    fn eq(&self, other: &i32) -> bool {
-        !self.is_na() && self.0 == *other
-    }
-}
-
 impl TryFrom<Robj> for Rint {
     type Error = Error;
 
@@ -275,10 +110,3 @@ impl TryFrom<Robj> for Rint {
         Err(Error::ExpectedNumeric(robj))
     }
 }
-
-impl From<Rint> for Robj {
-    /// Convert am Rint into an robj.
-    fn from(value: Rint) -> Self {
-        Robj::from(value.0)
-    }
-}
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index 6e7afb196..974cc4c02 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -1,5 +1,43 @@
 use super::*;
 
+macro_rules! make_from_iterator {
+    ($fn_name : ident, $make_class : ident, $impl : ident, $type : ty) => {
+        pub fn $fn_name<Iter>(iter: Iter) -> Altrep
+        where
+            Iter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
+            Iter::Item: Into<$type>,
+        {
+            impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> $impl for Iter
+            where
+                Iter::Item: Into<$type>,
+            {
+                fn elt(&self, index: usize) -> $type {
+                    self.clone().nth(index).unwrap().into()
+                }
+
+                fn get_region(&self, index: usize, data: &mut [$type]) -> usize {
+                    let len = self.len();
+                    if index > len {
+                        0
+                    } else {
+                        let mut iter = self.clone().skip(index);
+                        let num_elems = data.len().min(len - index);
+                        let dest = &mut data[0..num_elems];
+                        for d in dest.iter_mut() {
+                            *d = iter.next().unwrap().into();
+                        }
+                        num_elems
+                    }
+                }
+            }
+
+            let class = Altrep::$make_class::<Iter>(std::any::type_name::<Iter>(), "extendr");
+            let robj: Robj = Altrep::from_state_and_class(iter, class, false).into();
+            Altrep { robj }
+        }
+    };
+}
+
 #[derive(Debug, PartialEq, Clone)]
 pub struct Altrep {
     pub(crate) robj: Robj,
@@ -979,43 +1017,22 @@ impl Altrep {
         })
     }
 
-    pub fn make_altinteger_from_iterator<Iter>(iter: Iter) -> Altrep
-    where
-        Iter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
-        Iter::Item: Into<i32>,
-    {
-        impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> AltrepImpl for Iter {
-            fn length(&self) -> usize {
-                self.len()
-            }
-        }
-
-        impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> AltIntegerImpl for Iter
-        where
-            Iter::Item: Into<i32>,
-        {
-            fn elt(&self, index: usize) -> i32 {
-                self.clone().nth(index).unwrap().into()
-            }
-
-            fn get_region(&self, index: usize, data: &mut [i32]) -> usize {
-                let len = self.len();
-                if index > len {
-                    0
-                } else {
-                    let mut iter = self.clone().skip(index);
-                    let num_elems = data.len().min(len - index);
-                    let dest = &mut data[0..num_elems];
-                    for d in dest.iter_mut() {
-                        *d = iter.next().unwrap().into();
-                    }
-                    num_elems
-                }
-            }
-        }
+    make_from_iterator!(
+        make_altinteger_from_iterator,
+        make_altinteger_class,
+        AltIntegerImpl,
+        i32
+    );
+    make_from_iterator!(
+        make_altreal_from_iterator,
+        make_altreal_class,
+        AltRealImpl,
+        f64
+    );
+}
 
-        let class = Altrep::make_altinteger_class::<Iter>(std::any::type_name::<Iter>(), "extendr");
-        let robj: Robj = Altrep::from_state_and_class(iter, class, false).into();
-        Altrep { robj }
+impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> AltrepImpl for Iter {
+    fn length(&self) -> usize {
+        self.len()
     }
 }
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
new file mode 100644
index 000000000..aa20321d3
--- /dev/null
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -0,0 +1,94 @@
+use super::scalar::Rfloat;
+use super::*;
+use std::iter::FromIterator;
+
+/// An obscure `NA`-aware wrapper for R's double vectors.
+/// Can be used to iterate over vectors obtained from R
+/// or to create new vectors that can be returned back to R.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let mut vec = (0..5).map(|i| (i as f64).into()).collect::<Doubles>();
+///     vec.iter_mut().for_each(|v| *v = *v + 10f64);
+///     assert_eq!(vec.elt(0), 10f64);
+///     let sum = vec.iter().sum::<Rfloat>();
+///     assert_eq!(sum, 60f64);
+/// }
+/// ```  
+#[derive(Debug, PartialEq, Clone)]
+pub struct Doubles {
+    pub(crate) robj: Robj,
+}
+
+crate::wrapper::macros::gen_vector_wrapper_impl!(
+    Doubles, // Implements for
+    Rfloat,  // Element type
+    f64,     // Raw element type
+    REAL,    // `R` functions prefix
+    REALSXP, // `SEXP`
+    double   // Singular type name used in docs
+);
+
+#[cfg(test)]
+mod tests {
+    use crate::prelude::*;
+
+    #[test]
+    fn from_iterator() {
+        test! {
+            let vec : Doubles = (0..3).map(|i| (i as f64).into()).collect();
+            assert_eq!(vec, Doubles::from_values([0f64, 1f64, 2f64]));
+        }
+    }
+    #[test]
+    fn iter_mut() {
+        test! {
+            let mut vec = Doubles::from_values([0f64, 1f64, 2f64, 3f64]);
+            vec.iter_mut().for_each(|v| *v = *v + 1f64);
+            assert_eq!(vec, Doubles::from_values([1f64, 2f64, 3f64, 4f64]));
+        }
+    }
+
+    #[test]
+    fn iter() {
+        test! {
+            let vec = Doubles::from_values(0..3);
+            assert_eq!(vec.iter().sum::<Rfloat>(), 3f64);
+        }
+    }
+
+    #[test]
+    fn from_values_short() {
+        test! {
+            // Short (<64k) vectors are allocated.
+            let vec = Doubles::from_values((0..3).map(|i| 2f64 - i as f64));
+            assert_eq!(vec.is_altrep(), false);
+            assert_eq!(r!(vec.clone()), r!([2f64, 1f64, 0f64]));
+            assert_eq!(vec.elt(1), 1f64);
+            let mut dest = [0f64; 2];
+            vec.get_region(1, &mut dest);
+            assert_eq!(dest, [1f64, 0f64]);
+        }
+    }
+    #[test]
+    fn from_values_long() {
+        test! {
+            // Long (>=64k) vectors are lazy ALTREP objects.
+            let vec = Doubles::from_values((0..1000000000).map(|x| x as f64));
+            assert_eq!(vec.is_altrep(), true);
+            assert_eq!(vec.elt(12345678), 12345678f64);
+            let mut dest = [0f64; 2];
+            vec.get_region(12345678, &mut dest);
+            assert_eq!(dest, [12345678f64, 12345679f64]);
+        }
+    }
+
+    #[test]
+    fn new() {
+        test! {
+            let vec = Doubles::new(10);
+            assert_eq!(vec.is_real(), true);
+            assert_eq!(vec.len(), 10);
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/externalptr.rs b/extendr-api/src/wrapper/externalptr.rs
new file mode 100644
index 000000000..33837828b
--- /dev/null
+++ b/extendr-api/src/wrapper/externalptr.rs
@@ -0,0 +1,113 @@
+use super::*;
+use std::any::Any;
+
+/// Wrapper for creating R objects containing any Rust object.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let extptr = ExternalPtr::from_val(1);
+///     assert_eq!(*extptr, 1);
+///     let robj : Robj = extptr.into();
+///     let extptr2 : ExternalPtr<i32> = robj.try_into().unwrap();
+///     assert_eq!(*extptr2, 1);
+/// }
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct ExternalPtr<T> {
+    /// This is the contained Robj.
+    pub(crate) robj: Robj,
+
+    /// This is a zero-length object that holds the type of the object.
+    marker: std::marker::PhantomData<T>,
+}
+
+impl<T> Deref for ExternalPtr<T> {
+    type Target = T;
+
+    /// This allows us to treat the Robj as if it is the type T.
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*self.robj.external_ptr_addr::<T>() }
+    }
+}
+
+impl<T: Any> ExternalPtr<T> {
+    /// Construct an external pointer object from any type T.
+    /// In this case, the R object owns the data and will drop the Rust object
+    /// when the last reference is removed via register_c_finalizer.
+    ///
+    pub fn from_val(val: T) -> Self {
+        unsafe {
+            // This gets the type name of T as a string. eg. "i32".
+            let type_name = std::any::type_name::<T>();
+
+            // This allocates some memory for our object and moves the object into it.
+            let boxed = Box::new(val);
+
+            // This constructs an external pointer to our boxed data.
+            // into_raw() converts the box to a malloced pointer.
+            let robj = Robj::make_external_ptr(Box::into_raw(boxed), r!(type_name), r!(()));
+
+            extern "C" fn finalizer<T>(x: SEXP) {
+                unsafe {
+                    let ptr = R_ExternalPtrAddr(x) as *mut T;
+
+                    // Convert the pointer to a box and drop it implictly.
+                    // This frees up the memory we have used and calls the "T::drop" method if there is one.
+                    Box::from_raw(ptr);
+                }
+            }
+
+            // Tell R about our finalizer
+            robj.register_c_finalizer(Some(finalizer::<T>));
+
+            // Return an object in a wrapper.
+            Self {
+                robj,
+                marker: std::marker::PhantomData,
+            }
+        }
+    }
+
+    // TODO: make a constructor for references?
+
+    /// Get the "tag" of an external pointer. This is the type name in the common case.
+    pub fn external_ptr_tag(&self) -> Robj {
+        unsafe { new_owned(R_ExternalPtrTag(self.robj.get())) }
+    }
+
+    /// Get the "protected" field of an external pointer. This is NULL in the common case.
+    pub fn external_ptr_protected(&self) -> Robj {
+        unsafe { new_owned(R_ExternalPtrProtected(self.robj.get())) }
+    }
+}
+
+impl<T: Any> TryFrom<Robj> for ExternalPtr<T> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if robj.rtype() != RType::ExternalPtr {
+            return Err(Error::ExpectedExternalPtr(robj));
+        }
+
+        let res = ExternalPtr::<T> {
+            robj,
+            marker: std::marker::PhantomData,
+        };
+
+        // Check the type name.
+        let type_name = std::any::type_name::<T>();
+        if res.external_ptr_tag().as_str() != Some(type_name) {
+            return Err(Error::ExpectedExternalPtrType(res.robj, type_name.into()));
+        }
+
+        Ok(res)
+    }
+}
+
+impl<T: Any> From<ExternalPtr<T>> for Robj {
+    fn from(val: ExternalPtr<T>) -> Self {
+        val.robj
+    }
+}
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index e23ab5f00..abc88786f 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -2,162 +2,95 @@ use super::scalar::Rint;
 use super::*;
 use std::iter::FromIterator;
 
+/// An obscure `NA`-aware wrapper for R's integer vectors.
+/// Can be used to iterate over vectors obtained from R
+/// or to create new vectors that can be returned back to R.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let mut vec = (0..5).map(|i| i.into()).collect::<Integers>();
+///     vec.iter_mut().for_each(|v| *v = *v + 10);
+///     assert_eq!(vec.elt(0), 10);
+///     let sum = vec.iter().sum::<Rint>();
+///     assert_eq!(sum, 60);
+/// }
+/// ```  
 #[derive(Debug, PartialEq, Clone)]
 pub struct Integers {
     pub(crate) robj: Robj,
 }
 
-impl Default for Integers {
-    fn default() -> Self {
-        Integers::new(0)
-    }
-}
-
-// Under this size, vectors are manifest.
-// Above this size, vectors are lazy ALTREP objects.
-const SHORT_VECTOR_LENGTH: usize = 64 * 1024;
-
-impl Integers {
-    /// Create a new vector of integers.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let vec = Integers::new(10);
-    ///     assert_eq!(vec.is_integer(), true);
-    ///     assert_eq!(vec.len(), 10);
-    /// }
-    /// ```
-    pub fn new(len: usize) -> Integers {
-        let iter = (0..len).map(|_| 0);
-        Integers::from_values(iter)
-    }
-
-    /// Wrapper for creating ALTREP integer (INTSXP) vectors from iterators.
-    /// The iterator must be exact, clonable and implement Debug.
-    ///
-    /// If you want a more generalised constructor, use `iter.collect::<Integers>()`.
-    ///
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     // Short (<64k) vectors are allocated.
-    ///     let vec = Integers::from_values((0..3).map(|i| 2-i));
-    ///     assert_eq!(vec.is_altrep(), false);
-    ///     assert_eq!(r!(vec.clone()), r!([2, 1, 0]));
-    ///     assert_eq!(vec.elt(1), 1);
-    ///     let mut dest = [0; 2];
-    ///     vec.get_region(1, &mut dest);
-    ///     assert_eq!(dest, [1, 0]);
-    ///
-    ///     // Long (>=64k) vectors are lazy ALTREP objects.
-    ///     let vec = Integers::from_values(0..1000000000);
-    ///     assert_eq!(vec.is_altrep(), true);
-    ///     assert_eq!(vec.elt(12345678), 12345678);
-    ///     let mut dest = [0; 2];
-    ///     vec.get_region(12345678, &mut dest);
-    ///     assert_eq!(dest, [12345678, 12345679]);
-    /// }
-    /// ```
-    pub fn from_values<V>(values: V) -> Self
-    where
-        V: IntoIterator,
-        V::IntoIter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
-        V::Item: Into<i32>,
-    {
-        single_threaded(|| {
-            let values: V::IntoIter = values.into_iter();
-
-            let robj = if values.len() >= SHORT_VECTOR_LENGTH {
-                Altrep::make_altinteger_from_iterator(values)
-                    .try_into()
-                    .unwrap()
-            } else {
-                let mut robj = Robj::alloc_vector(INTSXP, values.len());
-                let dest: &mut [i32] = robj.as_typed_slice_mut().unwrap();
-
-                for (d, v) in dest.iter_mut().zip(values) {
-                    *d = v.into();
-                }
-                robj
-            };
-            Self { robj }
-        })
-    }
-
-    /// Get a single element from the vector.
-    /// Note that this is very inefficient in a tight loop.
-    pub fn elt(&self, index: usize) -> Rint {
-        unsafe { INTEGER_ELT(self.get(), index as R_xlen_t).into() }
-    }
-
-    /// Get a region of elements from the vector.
-    pub fn get_region(&self, index: usize, dest: &mut [i32]) {
-        unsafe {
-            let ptr = dest.as_mut_ptr();
-            INTEGER_GET_REGION(self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr);
+crate::wrapper::macros::gen_vector_wrapper_impl!(
+    Integers, // Implements for
+    Rint,     // Element type
+    i32,      // Raw element type
+    INTEGER,  // `R` functions prefix
+    INTSXP,   // `SEXP`
+    integer   // Singular type name used in docs
+);
+
+#[cfg(test)]
+mod tests {
+    use crate::prelude::*;
+
+    #[test]
+    fn from_iterator() {
+        test! {
+            let vec : Integers = (0..3).map(|i| i.into()).collect();
+            assert_eq!(vec, Integers::from_values([0, 1, 2]));
         }
     }
 
-    /// Return TRUE if the vector is sorted, FALSE if not, or NA_BOOL if unknown.
-    pub fn is_sorted(&self) -> Bool {
-        unsafe { INTEGER_IS_SORTED(self.get()).into() }
+    #[test]
+    fn iter_mut() {
+        test! {
+            let mut vec = Integers::from_values(0..3);
+            vec.iter_mut().for_each(|v| *v = *v + 1);
+            assert_eq!(vec, Integers::from_values(1..4));
+        }
     }
 
-    /// Return TRUE if the vector has NAs, FALSE if not, or NA_BOOL if unknown.
-    pub fn no_na(&self) -> Bool {
-        unsafe { INTEGER_NO_NA(self.get()).into() }
+    #[test]
+    fn iter() {
+        test! {
+            let vec = Integers::from_values(0..3);
+            assert_eq!(vec.iter().sum::<Rint>(), 3);
+        }
     }
 
-    /// Return an iterator for for an integer object.
-    /// Forces ALTREP objects to manifest.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let vec = Integers::from_values(0..3);
-    ///     assert_eq!(vec.iter().sum::<Rint>(), 3);
-    /// }
-    /// ```
-    pub fn iter(&self) -> impl Iterator<Item = Rint> {
-        self.as_typed_slice().unwrap().iter().cloned()
+    #[test]
+    fn from_values_short() {
+        test! {
+            // Short (<64k) vectors are allocated.
+            let vec = Integers::from_values((0..3).map(|i| 2-i));
+            assert_eq!(vec.is_altrep(), false);
+            assert_eq!(r!(vec.clone()), r!([2, 1, 0]));
+            assert_eq!(vec.elt(1), 1);
+            let mut dest = [0; 2];
+            vec.get_region(1, &mut dest);
+            assert_eq!(dest, [1, 0]);
+        }
     }
 
-    /// Return a writable iterator for an integer object.
-    /// Forces ALTREP objects to manifest.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let mut vec = Integers::from_values(0..3);
-    ///     vec.iter_mut().for_each(|v| *v = *v + 1);
-    ///     assert_eq!(vec, Integers::from_values(1..4));
-    /// }
-    /// ```
-    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Rint> {
-        self.as_typed_slice_mut().unwrap().iter_mut()
+    #[test]
+    fn from_values_long() {
+        test! {
+            // Long (>=64k) vectors a lazy ALTREP objects.
+            let vec = Integers::from_values(0..1000000000);
+            assert_eq!(vec.is_altrep(), true);
+            assert_eq!(vec.elt(12345678), 12345678);
+            let mut dest = [0; 2];
+            vec.get_region(12345678, &mut dest);
+            assert_eq!(dest, [12345678, 12345679]);
+        }
     }
-}
-
-impl FromIterator<Rint> for Integers {
-    /// A more generalised iterator collector for small vectors.
-    /// Generates a non-ALTREP vector.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let vec : Integers = (0..3).map(|i| i.into()).collect();
-    ///     assert_eq!(vec, Integers::from_values([0, 1, 2]));
-    /// }
-    /// ```
-    fn from_iter<T: IntoIterator<Item = Rint>>(iter: T) -> Self {
-        // Collect into a vector first.
-        // TODO: specialise for ExactSizeIterator.
-        let values: Vec<Rint> = iter.into_iter().collect();
 
-        let mut robj = Robj::alloc_vector(INTSXP, values.len());
-        let dest: &mut [Rint] = robj.as_typed_slice_mut().unwrap();
-
-        for (d, v) in dest.iter_mut().zip(values) {
-            *d = v;
+    #[test]
+    fn new() {
+        test! {
+            let vec = Integers::new(10);
+            assert_eq!(vec.is_integer(), true);
+            assert_eq!(vec.len(), 10);
         }
-
-        Integers { robj }
     }
 }
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
new file mode 100644
index 000000000..dcc544abf
--- /dev/null
+++ b/extendr-api/src/wrapper/macros.rs
@@ -0,0 +1,155 @@
+/// Generates `impl` block and required traits for a vector type.
+macro_rules! gen_vector_wrapper_impl {
+    (
+        /// Vector type for which traits are implemented.
+        $type : ident,
+        /// `NA`-aware element type.
+        $type_elem : ty,
+        /// Underlying primitive type.
+        $type_prim : ty,
+        /// `R` type name.
+        $r_type : ident,
+        /// `R` `SEXP`.
+        $sexp : ident,
+        /// Singular name of the type, used in doc strings.
+        $doc_name : ident
+    ) => {
+
+        // Under this size, vectors are manifest.
+        // Above this size, vectors are lazy ALTREP objects.
+        const SHORT_VECTOR_LENGTH: usize = 64 * 1024;
+
+        impl Default for $type {
+            fn default() -> Self {
+                $type::new(0)
+            }
+        }
+
+        impl $type {
+            paste::paste!{
+                #[doc = "Create a new vector of " $type:lower "."]
+                #[doc = "```"]
+                #[doc = "use extendr_api::prelude::*;"]
+                #[doc = "test! {"]
+                #[doc = "   let vec = " $type "::new(10);"]
+                #[doc = "   assert_eq!(vec.is_" $r_type:lower "(), true);"]
+                #[doc = "   assert_eq!(vec.len(), 10);"]
+                #[doc = "}"]
+                #[doc = "```"]
+                pub fn new(len: usize) -> $type {
+                    // TODO: Check if impacts performance.
+                    let iter = (0..len).map(|_| <$type_prim>::default());
+                    <$type>::from_values(iter)
+                }
+            }
+            paste::paste!{
+                #[doc = "Wrapper for creating ALTREP " $doc_name " (" $sexp ") vectors from iterators."]
+                #[doc = "The iterator must be exact, cloneable and implement Debug."]
+                #[doc = "If you want a more generalised constructor, use `iter.collect::<" $type ">()`."]
+                pub fn from_values<V>(values: V) -> Self
+                where
+                    V: IntoIterator,
+                    V::IntoIter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
+                    V::Item: Into<$type_prim>,
+                {
+                    single_threaded(|| {
+                        let values: V::IntoIter = values.into_iter();
+
+                        let robj = if values.len() >= SHORT_VECTOR_LENGTH {
+                            Altrep::[<make_alt $r_type:lower _from_iterator>](values)
+                                .try_into()
+                                .unwrap()
+                        } else {
+                            let mut robj = Robj::alloc_vector($sexp, values.len());
+                            let dest: &mut [$type_prim] = robj.as_typed_slice_mut().unwrap();
+
+                            for (d, v) in dest.iter_mut().zip(values) {
+                                *d = v.into();
+                            }
+                            robj
+                        };
+                        Self { robj }
+                    })
+                }
+            }
+
+            paste::paste! {
+                #[doc = "Get a single element from the vector."]
+                #[doc = "Note that this is very inefficient in a tight loop."]
+                #[doc = "```"]
+                #[doc = "use extendr_api::prelude::*;"]
+                #[doc = "test! {"]
+                #[doc = "   let vec = " $type "::new(10);"]
+                #[doc = "   assert_eq!(vec.elt(0), <"$type_elem">::default());"]
+                #[doc = "   assert_eq!(vec.elt(9), <"$type_elem">::default());"]
+                #[doc = "   assert!(vec.elt(10).is_na());"]
+                #[doc = "}"]
+                #[doc = "```"]
+                pub fn elt(&self, index: usize) -> $type_elem {
+                    // Defensive check for oob
+                    // This check will not be needed in later releases of R
+                    if(index >= self.len()) {
+                        <$type_elem>::na()
+                    } else {
+                        unsafe { [<$r_type _ELT>](self.get(), index as R_xlen_t).into() }
+                    }
+                }
+            }
+
+            /// Get a region of elements from the vector.
+            pub fn get_region(&self, index: usize, dest: &mut [$type_prim]) {
+                unsafe {
+                    let ptr = dest.as_mut_ptr();
+                    paste::paste!{ [<$r_type _GET_REGION>](self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr); }
+                }
+            }
+
+            /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
+            pub fn is_sorted(&self) -> Bool {
+                unsafe { paste::paste!{ [<$r_type _IS_SORTED>](self.get()).into() } }
+            }
+
+            /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
+            pub fn no_na(&self) -> Bool {
+                unsafe { paste::paste!{ [<$r_type _NO_NA>](self.get()).into() } }
+            }
+
+            paste::paste!{
+                #[doc = "Return an iterator for a " $doc_name " object."]
+                #[doc = "Forces ALTREP objects to manifest."]
+                pub fn iter(&self) -> impl Iterator<Item = $type_elem> {
+                    self.as_typed_slice().unwrap().iter().cloned()
+                }
+            }
+
+            paste::paste!{
+                #[doc = "Return a writable iterator for a " $doc_name " object."]
+                #[doc = "Forces ALTREP objects to manifest."]
+                pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut $type_elem> {
+                    self.as_typed_slice_mut().unwrap().iter_mut()
+                }
+            }
+        }
+
+        impl FromIterator<$type_elem> for $type {
+            /// A more generalised iterator collector for small vectors.
+            /// Generates a non-ALTREP vector.
+            fn from_iter<T: IntoIterator<Item = $type_elem>>(iter: T) -> Self {
+                // Collect into a vector first.
+                // TODO: specialise for ExactSizeIterator.
+                let values: Vec<$type_elem> = iter.into_iter().collect();
+
+                let mut robj = Robj::alloc_vector($sexp, values.len());
+                let dest: &mut [$type_elem] = robj.as_typed_slice_mut().unwrap();
+
+                for (d, v) in dest.iter_mut().zip(values) {
+                    *d = v;
+                }
+
+                $type { robj }
+            }
+        }
+    }
+}
+
+pub(in crate::wrapper) use gen_vector_wrapper_impl;
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index fc729ca54..7792f5f19 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -6,12 +6,15 @@ use libR_sys::*;
 
 pub mod altrep;
 pub mod char;
+pub mod doubles;
 pub mod environment;
 pub mod expr;
+pub mod externalptr;
 pub mod function;
 pub mod integers;
 pub mod lang;
 pub mod list;
+mod macros;
 pub mod matrix;
 pub mod nullable;
 pub mod pairlist;
@@ -26,8 +29,10 @@ pub use altrep::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
     AltrepImpl,
 };
+pub use doubles::Doubles;
 pub use environment::{EnvIter, Environment};
 pub use expr::Expression;
+pub use externalptr::ExternalPtr;
 pub use function::Function;
 pub use integers::Integers;
 pub use lang::Language;
@@ -167,6 +172,7 @@ make_conversions!(Altrep, ExpectedAltrep, is_altrep, "Not an Altrep type");
 make_conversions!(S4, ExpectedS4, is_s4, "Not a S4 type");
 
 make_conversions!(Integers, ExpectedInteger, is_integer, "Not an integer type");
+make_conversions!(Doubles, ExpectedReal, is_real, "Not a floating point type");
 
 impl Robj {
     /// Convert a symbol object to a Symbol wrapper.


Test patch:
diff --git a/extendr-api/tests/externalptr_test.rs b/extendr-api/tests/externalptr_test.rs
new file mode 100644
index 000000000..35c9be4fa
--- /dev/null
+++ b/extendr-api/tests/externalptr_test.rs
@@ -0,0 +1,48 @@
+use extendr_api::prelude::*;
+use lazy_static::lazy_static;
+
+#[test]
+fn test_externalptr() {
+    test! {
+        let extptr = ExternalPtr::from_val(1);
+        assert_eq!(*extptr, 1);
+        let robj : Robj = extptr.into();
+        let extptr2 : ExternalPtr<i32> = robj.try_into().unwrap();
+        assert_eq!(*extptr2, 1);
+    }
+}
+
+#[test]
+fn test_externalptr_drop() {
+    test! {
+        // This flag will get set when we do the drop.
+        lazy_static! {
+            static ref Z : std::sync::Mutex<bool> = std::sync::Mutex::new(false);
+        }
+
+        // Dummy structure that will show if we drop correctly.
+        struct X {
+        }
+
+        // Check that drop() is called after the owning object is dropped.
+        impl Drop for X {
+            fn drop(&mut self) {
+                // Set the flag to show that we have dropped.
+                let mut lck = Z.lock().unwrap();
+                *lck = true;
+            }
+        }
+
+        // Create an external pointer to test the drop.
+        let extptr = ExternalPtr::from_val(X {});
+
+        // The object should be protected here - drop not called yet.
+        R!("gc()").unwrap();
+        assert_eq!(*Z.lock().unwrap(), false);
+
+        // Dropping the pointer should allow gc to drop the object.
+        drop(extptr);
+        R!("gc()").unwrap();
+        assert_eq!(*Z.lock().unwrap(), true);
+    }
+}
diff --git a/extendr-api/tests/scalar_tests.rs b/extendr-api/tests/scalar_tests.rs
index 708b38d0f..381e6edc1 100644
--- a/extendr-api/tests/scalar_tests.rs
+++ b/extendr-api/tests/scalar_tests.rs
@@ -21,30 +21,106 @@ fn test_rint() {
     // NA lhs
     let a = Rint::na();
     let b = Rint::from(10);
-    assert_eq!(a + b, Rint::na());
-    assert_eq!(a - b, Rint::na());
-    assert_eq!(a * b, Rint::na());
-    assert_eq!(a / b, Rint::na());
-    assert_eq!(-a, Rint::na());
-    assert_eq!(!a, Rint::na());
+    assert!((a + b).is_na());
+    assert!((a - b).is_na());
+    assert!((a * b).is_na());
+    assert!((a / b).is_na());
+    assert!((-a).is_na());
+    assert!((!a).is_na());
 
     // NA rhs
     let a = Rint::from(10);
     let b = Rint::na();
-    assert_eq!(a + b, Rint::na());
-    assert_eq!(a - b, Rint::na());
-    assert_eq!(a * b, Rint::na());
-    assert_eq!(a / b, Rint::na());
+    assert!((a + b).is_na());
+    assert!((a - b).is_na());
+    assert!((a * b).is_na());
+    assert!((a / b).is_na());
 
     // Overflow
     let a = Rint::from(i32::MAX - 1);
     let b = Rint::from(10);
-    assert_eq!(a * b, Rint::na());
-    assert_eq!(Rint::from(1) / Rint::from(0), Rint::na());
-    assert_eq!(Rint::from(-1) / Rint::na(), Rint::na());
+    assert!((a * b).is_na());
+    assert!((Rint::from(1) / Rint::from(0)).is_na());
+    assert!((Rint::from(-1) / Rint::na()).is_na());
 
     // Underflow
     let a = Rint::from(i32::MIN + 1);
     let b = Rint::from(-10);
-    assert_eq!(a + b, Rint::na());
+    assert!((a + b).is_na());
+}
+
+#[test]
+fn test_rfloat() {
+    let a = Rfloat::from(20.);
+    let b = Rfloat::from(10.);
+    assert_eq!(a + b, Rfloat::from(30.));
+    assert_eq!(a - b, Rfloat::from(10.));
+    assert_eq!(a * b, Rfloat::from(200.));
+    assert_eq!(a / b, Rfloat::from(2.));
+    assert_eq!(-a, Rfloat::from(-20.));
+
+    assert_eq!(&a + b, Rfloat::from(30.));
+    assert_eq!(&a - b, Rfloat::from(10.));
+    assert_eq!(&a * b, Rfloat::from(200.));
+    assert_eq!(&a / b, Rfloat::from(2.));
+    assert_eq!(-&a, Rfloat::from(-20.));
+
+    // NA lhs
+    let a = Rfloat::na();
+    let b = Rfloat::from(10.);
+    assert!((a + b).is_na());
+    assert!((a - b).is_na());
+    assert!((a * b).is_na());
+    assert!((a / b).is_na());
+    assert!((-a).is_na());
+
+    // NA rhs
+    let a = Rfloat::from(10.);
+    let b = Rfloat::na();
+    assert!((a + b).is_na());
+    assert!((a - b).is_na());
+    assert!((a * b).is_na());
+    assert!((a / b).is_na());
+
+    // Inf is a single value, so can be tested for equality
+    let a = Rfloat::from(f64::INFINITY);
+    let b = Rfloat::from(42.);
+    assert_eq!(a + b, a);
+    assert_eq!(a - b, a);
+    assert_eq!(b - a, Rfloat::from(f64::NEG_INFINITY));
+    assert_eq!(a * b, a);
+    assert_eq!(a / b, a);
+    assert_eq!(-a, Rfloat::from(f64::NEG_INFINITY));
+
+    let a = Rfloat::from(f64::NEG_INFINITY);
+    assert_eq!(a + b, a);
+    assert_eq!(a - b, a);
+    assert_eq!(b - a, Rfloat::from(f64::INFINITY));
+    assert_eq!(a * b, a);
+    assert_eq!(a / b, a);
+    assert_eq!(-a, Rfloat::from(f64::INFINITY));
+
+    // Operations with NaN produce NaN
+    let a = Rfloat::from(f64::NAN);
+    assert!((a + b).is_nan());
+    assert!((a - b).is_nan());
+    assert!((a * b).is_nan());
+    assert!((a / b).is_nan());
+    assert!((-a).is_nan());
+
+    // Signs
+    assert!(Rfloat::from(0.).is_sign_positive());
+    assert!(Rfloat::from(f64::INFINITY).is_sign_positive());
+
+    assert!(Rfloat::from(-0.).is_sign_negative());
+    assert!(Rfloat::from(f64::NEG_INFINITY).is_sign_negative());
+
+    // Infinity
+    assert!(Rfloat::from(f64::INFINITY).is_infinite());
+    assert!(Rfloat::from(f64::NEG_INFINITY).is_infinite());
+    assert!(!Rfloat::from(0.).is_infinite());
+
+    // Some more, testing mixed binary operators
+    assert!((Rfloat::from(f64::INFINITY) + 1.).is_infinite());
+    assert!((42. - Rfloat::from(f64::INFINITY)).is_sign_negative());
 }
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index 80055d860..dcf9dfd02 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -30,6 +30,14 @@ try_double_vec <- function(x) .Call(wrap__try_double_vec, x)
 
 try_list_str_hash <- function(x) .Call(wrap__try_list_str_hash, x)
 
+get_doubles_element <- function(x, i) .Call(wrap__get_doubles_element, x, i)
+
+get_integers_element <- function(x, i) .Call(wrap__get_integers_element, x, i)
+
+doubles_square <- function(input) .Call(wrap__doubles_square, input)
+
+integers_square <- function(input) .Call(wrap__integers_square, input)
+
 #' Test whether `_arg` parameters are treated correctly in R
 #' Executes \code{`_x` - `_y`}
 #' @param _x an integer scalar, ignored
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index b251d56ba..a4a5fcf5f 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -1,7 +1,7 @@
 use extendr_api::prelude::*;
 mod submodule;
-use submodule::*;
 use std::collections::HashMap;
+use submodule::*;
 
 // Return string `"Hello world!"` to R.
 #[extendr]
@@ -11,9 +11,7 @@ fn hello_world() -> &'static str {
 
 // Do nothing.
 #[extendr]
-fn do_nothing() {
-}
-
+fn do_nothing() {}
 
 // From: input/output conversion
 
@@ -22,40 +20,53 @@ fn do_nothing() {
 // Convert a double scalar to itself
 // x a number
 #[extendr]
-fn double_scalar(x: f64) -> f64 { x }
+fn double_scalar(x: f64) -> f64 {
+    x
+}
 
 // Convert an int scalar to itself
 // x a number
 #[extendr]
-fn int_scalar(x: i32) -> i32 { x }
+fn int_scalar(x: i32) -> i32 {
+    x
+}
 
 // Convert a bool scalar to itself
 // x a number
 #[extendr]
-fn bool_scalar(x: bool) -> bool { x }
+fn bool_scalar(x: bool) -> bool {
+    x
+}
 
 // Convert a string to itself
 // x a string
 #[extendr]
-fn char_scalar(x: String) -> String { x }
+fn char_scalar(x: String) -> String {
+    x
+}
 
 // Convert a vector of strings to itself
 // x a vector of strings
 #[extendr]
-fn char_vec(x: Vec<String>) -> Vec<String> {x}
+fn char_vec(x: Vec<String>) -> Vec<String> {
+    x
+}
 
 // Convert a numeric vector to itself
 // x a numeric vector
 #[extendr]
-fn double_vec(x: Vec<f64>) -> Vec<f64> {x}
-
+fn double_vec(x: Vec<f64>) -> Vec<f64> {
+    x
+}
 
 // Non-atomic types
 
 // Convert a list to a HashMap<&str, Robj> in rust and back. Does not preserve list order
 // x a list
 #[extendr]
-fn list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {x}
+fn list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {
+    x
+}
 
 // TryFrom: conversions
 
@@ -64,14 +75,51 @@ fn list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {x}
 // Convert a vector of doubles to itself
 // x a vector of doubles
 #[extendr(use_try_from = true)]
-fn try_double_vec(x: Vec<f64>) -> Vec<f64> {x}
+fn try_double_vec(x: Vec<f64>) -> Vec<f64> {
+    x
+}
 
 // Non-atomic types
 
 // Convert a list to a HashMap<&str, Robj> in rust and back. Does not preserve list order
 // x a list
 #[extendr(use_try_from = true)]
-fn try_list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {x}
+fn try_list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {
+    x
+}
+
+// Vector wrappers
+#[extendr(use_try_from = true)]
+fn get_doubles_element(x: Doubles, i: i32) -> Rfloat {
+    x.elt(i as usize)
+}
+
+#[extendr(use_try_from = true)]
+fn get_integers_element(x: Integers, i: i32) -> Rint {
+    x.elt(i as usize)
+}
+
+#[extendr(use_try_from = true)]
+fn doubles_square(input: Doubles) -> Doubles {
+    let mut result = Doubles::new(input.len());
+
+    for (x, y) in result.iter_mut().zip(input.iter()) {
+        *x = y * y;
+    }
+
+    result
+}
+
+#[extendr(use_try_from = true)]
+fn integers_square(input: Integers) -> Integers {
+    let mut result = Integers::new(input.len());
+
+    for (x, y) in result.iter_mut().zip(input.iter()) {
+        *x = y * y;
+    }
+
+    result
+}
 
 // Parsing
 
@@ -83,7 +131,9 @@ fn try_list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {x}
 /// @param `_y` an integer scalar, ignored
 /// @export
 #[extendr]
-fn special_param_names(_x : i32, _y : i32) -> i32 { _x - _y }
+fn special_param_names(_x: i32, _y: i32) -> i32 {
+    _x - _y
+}
 
 /// Test wrapping of special function name
 /// @name f__00__special_function_name
@@ -111,18 +161,18 @@ impl MyClass {
     fn new() -> Self {
         Self { a: 0 }
     }
-    
+
     /// Method for setting stuff.
     /// @param x a number
-    fn set_a(& mut self, x: i32) {
+    fn set_a(&mut self, x: i32) {
         self.a = x;
     }
-    
+
     /// Method for getting stuff.
     fn a(&self) -> i32 {
         self.a
     }
-    
+
     /// Method for getting one's self.
     fn me(&self) -> &Self {
         self
@@ -131,8 +181,7 @@ impl MyClass {
 
 // Class for testing special names
 #[derive(Default, Debug)]
-struct __MyClass {
-}
+struct __MyClass {}
 
 // Class for testing special names
 // Unexported because of documentation conflict
@@ -146,7 +195,6 @@ impl __MyClass {
     fn __name_test(&self) {}
 }
 
-
 // Class for testing (unexported)
 #[derive(Default, Debug)]
 struct MyClassUnexported {
@@ -160,7 +208,7 @@ impl MyClassUnexported {
     fn new() -> Self {
         Self { a: 22 }
     }
-    
+
     /// Method for getting stuff.
     fn a(&self) -> i32 {
         self.a
@@ -177,7 +225,7 @@ extendr_module! {
     fn int_scalar;
     fn bool_scalar;
     fn char_scalar;
-    
+
     fn char_vec;
     fn double_vec;
 
@@ -186,12 +234,18 @@ extendr_module! {
     fn try_double_vec;
     fn try_list_str_hash;
 
+    fn get_doubles_element;
+    fn get_integers_element;
+
+    fn doubles_square;
+    fn integers_square;
+
     fn special_param_names;
     fn __00__special_function_name;
 
     impl MyClass;
     impl __MyClass;
     impl MyClassUnexported;
-    
+
     use submodule;
 }
diff --git a/tests/extendrtests/tests/testthat/test-vector-wrappers.R b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
new file mode 100644
index 000000000..db50842fa
--- /dev/null
+++ b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
@@ -0,0 +1,37 @@
+test_that("Access elements of double vector", {
+    x <- c(42.0, NA_real_, 100.0)
+    expect_equal(get_doubles_element(x, 0), 42.0)
+    expect_equal(get_doubles_element(x, 2), 100.0)
+    # Retrieving NA
+    expect_true(is.na(get_doubles_element(x, 1)))
+    # OOB returns NA
+    expect_true(is.na(get_doubles_element(x, 3)))
+})
+
+test_that("Access elements of integer vector", {
+    x <- c(42L, NA_integer_, 100L)
+    expect_equal(get_integers_element(x, 0), 42L)
+    expect_equal(get_integers_element(x, 2), 100L)
+    # Retrieving NA
+    expect_true(is.na(get_integers_element(x, 1)))
+    # OOB returns NA
+    expect_true(is.na(get_integers_element(x, 3)))
+})
+
+test_that("Construct double vector from squares of given values", {
+    x <- c(1.0 * (1:100))
+    expect_equal(doubles_square(x), x * x)
+})
+
+test_that("Construct integer vector from squares of given values", {
+    x <- c(1:100)
+    expect_equal(integers_square(x), x * x)
+})
+
+test_that("Double argument type safety", {
+    expect_error(doubles_square(1L))
+})
+
+test_that("Integer argument type safety", {
+    expect_error(integers_square(1.5))
+})
\ No newline at end of file