Repository: extendr/extendr
Pull request #320 (2021-11-10 18:06:33)

Create multiple traits for Robj and wrapper methods.
This is a major refactor of the implementation of the wrappers and Robj.

Currently, many of the wrappers implement `Deref<Target=Robj>` which makes making alternative
behaviour quite challenging.

For example, we would like to be able to index Integers with [i] and environments with ["str"]
ExternalPtr<T> should behave like a Box<T> enabling the contained element to be referenced
without calling a function.

This also starts to build a consistent interface for the wrappers which is expressed in traits.

We now only have two `impl Robj` blocks and one will be phased out.

Conversions between wrappers and Robj should be unified.

This PR should have very little effect on the visible interface, but should help us to start
cleaning up a bit.

There are a number of new traits that encapsulate existing functionality.

### GetSexp

controls access to the underlying SEXP pointer.

### Types 

Generates the new Rtype and Rany enums

### Length

Implements len()

### Conversions

Likely to be phased out has a large number of as_xxx() conversions.

### Rinternals

Has a number of internal R functions such as find_var()

### Slices

Gives unsafe access to DATAPTR in vectors.

### Operators

Implements R operators such as dollar()

 

Ilia-Kosenkov: Could we create a markdown in the docs that lists descriptions of all the traits? Like what it represents and where it is used/should be used in the future. It is rather hard to keep track of all the things, and this PR introduces even more traits, so perhaps it is a good place for start?
andy-thomason: Yes, we should make some better documentation now it is better organised.

We should write a guide to all the traits.

I would like to remove a lot of the old redundant `impl Robj` methods in favour
of a more consistent interface around the wrapper types.



andy-thomason: There are also a lot of our vector methods such as set_names() and elt() which should be in traits.

It should shrink down the function documentation somewhat, too.

Ilia-Kosenkov: Great. We have a mix of new and well-designed APIs and old outdated ones that honestly significantly slow my development process, so some docs or deprecation could be nice. 

I will try to review this PR as soon as I can.

Patch:
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index cd267ed96..a538dc496 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -1,5 +1,6 @@
 //! Error handling in Rust called from R.
 
+use crate::robj::Types;
 use crate::{throw_r_error, Robj};
 
 use std::convert::Infallible;
diff --git a/extendr-api/src/iter.rs b/extendr-api/src/iter.rs
index 4aa965bf9..135ab38a5 100644
--- a/extendr-api/src/iter.rs
+++ b/extendr-api/src/iter.rs
@@ -135,7 +135,7 @@ impl_iter_debug!(PairlistIter);
 impl_iter_debug!(StrIter);
 impl_iter_debug!(EnvIter);
 
-impl Robj {
+pub trait AsStrIter: GetSexp + Types + Length + Attributes + Rinternals {
     /// Get an iterator over a string vector.
     /// Returns None if the object is not a string vector
     /// but works for factors.
@@ -163,13 +163,13 @@ impl Robj {
     ///     assert_eq!(obj.as_str_iter().unwrap().map(|s| s.is_na()).collect::<Vec<_>>(), vec![false, false, false]);
     /// }
     /// ```
-    pub fn as_str_iter(&self) -> Option<StrIter> {
+    fn as_str_iter(&self) -> Option<StrIter> {
         let i = 0;
         let len = self.len();
         match self.sexptype() {
             STRSXP => unsafe {
                 Some(StrIter {
-                    vector: self.into(),
+                    vector: self.as_robj().clone(),
                     i,
                     len,
                     levels: R_NilValue,
@@ -179,7 +179,7 @@ impl Robj {
                 if let Some(levels) = self.get_attrib(levels_symbol()) {
                     if self.is_factor() && levels.sexptype() == STRSXP {
                         Some(StrIter {
-                            vector: self.into(),
+                            vector: self.as_robj().clone(),
                             i,
                             len,
                             levels: levels.get(),
@@ -195,3 +195,5 @@ impl Robj {
         }
     }
 }
+
+impl AsStrIter for Robj {}
diff --git a/extendr-api/src/lang_macros.rs b/extendr-api/src/lang_macros.rs
index 3c69da6c7..9d5c636e6 100644
--- a/extendr-api/src/lang_macros.rs
+++ b/extendr-api/src/lang_macros.rs
@@ -3,6 +3,7 @@
 
 use libR_sys::*;
 //use crate::robj::*;
+use crate::robj::GetSexp;
 use crate::robj::Robj;
 use crate::single_threaded;
 
@@ -115,6 +116,7 @@ macro_rules! lang {
     };
     ($sym : expr, $($rest: tt)*) => {
         unsafe {
+            use extendr_api::robj::GetSexp;
             let res = make_lang($sym);
             let mut tail = res.get();
             append_lang!(tail, $($rest)*);
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 42cffb4b7..17bd01b95 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -46,7 +46,12 @@ pub use super::wrapper::{
 
 pub use super::wrapper::s4::S4;
 
-pub use super::robj::{IntoRobj, Robj, RobjItertools};
+pub use super::wrapper::{Conversions, MatrixConversions};
+
+pub use super::robj::{
+    AsStrIter, Attributes, Eval, GetSexp, IntoRobj, Length, Operators, Rinternals, Robj,
+    RobjItertools, Slices, Types,
+};
 
 pub use super::thread_safety::{
     catch_r_error, handle_panic, single_threaded, this_thread_id, throw_r_error,
diff --git a/extendr-api/src/robj/from_robj.rs b/extendr-api/src/robj/from_robj.rs
index cfaf32c98..e4d06b889 100644
--- a/extendr-api/src/robj/from_robj.rs
+++ b/extendr-api/src/robj/from_robj.rs
@@ -1,4 +1,5 @@
 use super::*;
+use crate::wrapper::matrix::MatrixConversions;
 
 /// Trait used for incomming parameter conversion.
 pub trait FromRobj<'a>: Sized {
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 3bd1ee268..25f07d98e 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -33,8 +33,9 @@ mod tests;
 pub use from_robj::*;
 pub use into_robj::*;
 pub use iter::*;
+pub use operators::Operators;
 pub use operators::*;
-pub use rinternals::*;
+pub use rinternals::Rinternals;
 
 /// Wrapper for an R S-expression pointer (SEXP).
 ///
@@ -121,6 +122,94 @@ impl Default for Robj {
     }
 }
 
+pub trait GetSexp {
+    /// Get a copy of the underlying SEXP.
+    ///
+    /// # Safety
+    ///
+    /// Access to a raw SEXP pointer can cause undefined behaviour and is not thread safe.
+    unsafe fn get(&self) -> SEXP;
+
+    /// Get a reference to a Robj for this type.
+    fn as_robj(&self) -> &Robj;
+
+    /// Get a mutable reference to a Robj for this type.
+    fn as_robj_mut(&mut self) -> &mut Robj;
+}
+
+impl GetSexp for Robj {
+    unsafe fn get(&self) -> SEXP {
+        self.inner
+    }
+
+    fn as_robj(&self) -> &Robj {
+        unsafe { std::mem::transmute(&self.inner) }
+    }
+
+    fn as_robj_mut(&mut self) -> &mut Robj {
+        unsafe { std::mem::transmute(&mut self.inner) }
+    }
+}
+
+pub trait Slices: GetSexp {
+    /// Get an immutable slice to this object's data.
+    ///
+    /// # Safety
+    ///
+    /// Unless the type is correct, this will cause undefined behaviour.
+    /// Creating this slice will also instatiate and Altrep objects.
+    unsafe fn as_typed_slice_raw<T>(&self) -> &[T] {
+        let len = XLENGTH(self.get()) as usize;
+        let data = DATAPTR_RO(self.get()) as *const T;
+        std::slice::from_raw_parts(data, len)
+    }
+
+    /// Get a mutable slice to this object's data.
+    ///
+    /// # Safety
+    ///
+    /// Unless the type is correct, this will cause undefined behaviour.
+    /// Creating this slice will also instatiate and Altrep objects.
+    /// Not all obejects (especially not list and strings) support this.
+    unsafe fn as_typed_slice_raw_mut<T>(&mut self) -> &mut [T] {
+        let len = XLENGTH(self.get()) as usize;
+        let data = DATAPTR(self.get()) as *mut T;
+        std::slice::from_raw_parts_mut(data, len)
+    }
+}
+
+impl Slices for Robj {}
+
+pub trait Length: GetSexp {
+    /// Get the extended length of the object.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///
+    /// let a : Robj = r!(vec![1., 2., 3., 4.]);
+    /// assert_eq!(a.len(), 4);
+    /// }
+    /// ```
+    fn len(&self) -> usize {
+        unsafe { Rf_xlength(self.get()) as usize }
+    }
+
+    /// Returns `true` if the `Robj` contains no elements.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///
+    /// let a : Robj = r!(vec![0.; 0]); // length zero of numeric vector
+    /// assert_eq!(a.is_empty(), true);
+    /// }
+    /// ```
+    fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+}
+
+impl Length for Robj {}
+
 impl Robj {
     pub fn from_sexp(sexp: SEXP) -> Self {
         single_threaded(|| {
@@ -128,17 +217,12 @@ impl Robj {
             Robj { inner: sexp }
         })
     }
+}
 
-    /// Get a copy of the underlying SEXP.
-    /// Note: this is unsafe.
-    #[doc(hidden)]
-    pub unsafe fn get(&self) -> SEXP {
-        self.inner
-    }
-
+pub trait Types: GetSexp {
     #[doc(hidden)]
     /// Get the XXXSXP type of the object.
-    pub fn sexptype(&self) -> u32 {
+    fn sexptype(&self) -> u32 {
         unsafe { TYPEOF(self.get()) as u32 }
     }
 
@@ -164,7 +248,7 @@ impl Robj {
     ///     assert_eq!(r!(Raw::from_bytes(&[1_u8, 2, 3])).rtype(), RType::Raw);
     /// }
     /// ```
-    pub fn rtype(&self) -> RType {
+    fn rtype(&self) -> RType {
         match self.sexptype() {
             NILSXP => RType::Null,
             SYMSXP => RType::Symbol,
@@ -194,64 +278,42 @@ impl Robj {
         }
     }
 
-    pub fn as_any(&self) -> Rany {
+    fn as_any(&self) -> Rany {
         unsafe {
             match self.sexptype() {
-                NILSXP => Rany::Null(std::mem::transmute(self)),
-                SYMSXP => Rany::Symbol(std::mem::transmute(self)),
-                LISTSXP => Rany::Pairlist(std::mem::transmute(self)),
-                CLOSXP => Rany::Function(std::mem::transmute(self)),
-                ENVSXP => Rany::Environment(std::mem::transmute(self)),
-                PROMSXP => Rany::Promise(std::mem::transmute(self)),
-                LANGSXP => Rany::Language(std::mem::transmute(self)),
-                SPECIALSXP => Rany::Special(std::mem::transmute(self)),
-                BUILTINSXP => Rany::Builtin(std::mem::transmute(self)),
-                CHARSXP => Rany::Rstr(std::mem::transmute(self)),
-                LGLSXP => Rany::Logical(std::mem::transmute(self)),
-                INTSXP => Rany::Integer(std::mem::transmute(self)),
-                REALSXP => Rany::Real(std::mem::transmute(self)),
-                CPLXSXP => Rany::Complex(std::mem::transmute(self)),
-                STRSXP => Rany::String(std::mem::transmute(self)),
-                DOTSXP => Rany::Dot(std::mem::transmute(self)),
-                ANYSXP => Rany::Any(std::mem::transmute(self)),
-                VECSXP => Rany::List(std::mem::transmute(self)),
-                EXPRSXP => Rany::Expression(std::mem::transmute(self)),
-                BCODESXP => Rany::Bytecode(std::mem::transmute(self)),
-                EXTPTRSXP => Rany::ExternalPtr(std::mem::transmute(self)),
-                WEAKREFSXP => Rany::WeakRef(std::mem::transmute(self)),
-                RAWSXP => Rany::Raw(std::mem::transmute(self)),
-                S4SXP => Rany::S4(std::mem::transmute(self)),
-                _ => Rany::Unknown(std::mem::transmute(self)),
+                NILSXP => Rany::Null(std::mem::transmute(self.as_robj())),
+                SYMSXP => Rany::Symbol(std::mem::transmute(self.as_robj())),
+                LISTSXP => Rany::Pairlist(std::mem::transmute(self.as_robj())),
+                CLOSXP => Rany::Function(std::mem::transmute(self.as_robj())),
+                ENVSXP => Rany::Environment(std::mem::transmute(self.as_robj())),
+                PROMSXP => Rany::Promise(std::mem::transmute(self.as_robj())),
+                LANGSXP => Rany::Language(std::mem::transmute(self.as_robj())),
+                SPECIALSXP => Rany::Special(std::mem::transmute(self.as_robj())),
+                BUILTINSXP => Rany::Builtin(std::mem::transmute(self.as_robj())),
+                CHARSXP => Rany::Rstr(std::mem::transmute(self.as_robj())),
+                LGLSXP => Rany::Logical(std::mem::transmute(self.as_robj())),
+                INTSXP => Rany::Integer(std::mem::transmute(self.as_robj())),
+                REALSXP => Rany::Real(std::mem::transmute(self.as_robj())),
+                CPLXSXP => Rany::Complex(std::mem::transmute(self.as_robj())),
+                STRSXP => Rany::String(std::mem::transmute(self.as_robj())),
+                DOTSXP => Rany::Dot(std::mem::transmute(self.as_robj())),
+                ANYSXP => Rany::Any(std::mem::transmute(self.as_robj())),
+                VECSXP => Rany::List(std::mem::transmute(self.as_robj())),
+                EXPRSXP => Rany::Expression(std::mem::transmute(self.as_robj())),
+                BCODESXP => Rany::Bytecode(std::mem::transmute(self.as_robj())),
+                EXTPTRSXP => Rany::ExternalPtr(std::mem::transmute(self.as_robj())),
+                WEAKREFSXP => Rany::WeakRef(std::mem::transmute(self.as_robj())),
+                RAWSXP => Rany::Raw(std::mem::transmute(self.as_robj())),
+                S4SXP => Rany::S4(std::mem::transmute(self.as_robj())),
+                _ => Rany::Unknown(std::mem::transmute(self.as_robj())),
             }
         }
     }
+}
 
-    /// Get the extended length of the object.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///
-    /// let a : Robj = r!(vec![1., 2., 3., 4.]);
-    /// assert_eq!(a.len(), 4);
-    /// }
-    /// ```
-    pub fn len(&self) -> usize {
-        unsafe { Rf_xlength(self.get()) as usize }
-    }
-
-    /// Returns `true` if the `Robj` contains no elements.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///
-    /// let a : Robj = r!(vec![0.; 0]); // length zero of numeric vector
-    /// assert_eq!(a.is_empty(), true);
-    /// }
-    /// ```
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
+impl Types for Robj {}
 
+impl Robj {
     /// Is this object is an NA scalar?
     /// Works for character, integer and numeric types.
     /// ```
@@ -598,7 +660,9 @@ impl Robj {
             _ => None,
         }
     }
+}
 
+pub trait Eval: GetSexp {
     /// Evaluate the expression in R and return an error or an R object.
     /// ```
     /// use extendr_api::prelude::*;
@@ -608,7 +672,7 @@ impl Robj {
     ///    assert_eq!(add.eval().unwrap(), r!(3));
     /// }
     /// ```
-    pub fn eval(&self) -> Result<Robj> {
+    fn eval(&self) -> Result<Robj> {
         self.eval_with_env(&global_env())
     }
 
@@ -621,12 +685,12 @@ impl Robj {
     ///    assert_eq!(add.eval_with_env(&global_env()).unwrap(), r!(3));
     /// }
     /// ```
-    pub fn eval_with_env(&self, env: &Environment) -> Result<Robj> {
+    fn eval_with_env(&self, env: &Environment) -> Result<Robj> {
         single_threaded(|| unsafe {
             let mut error: raw::c_int = 0;
             let res = R_tryEval(self.get(), env.get(), &mut error as *mut raw::c_int);
             if error != 0 {
-                Err(Error::EvalError(self.clone()))
+                Err(Error::EvalError(Robj::from_sexp(self.get())))
             } else {
                 Ok(Robj::from_sexp(res))
             }
@@ -641,7 +705,7 @@ impl Robj {
     ///    assert_eq!(bad.eval_blind(), r!(NULL));
     /// }
     /// ```
-    pub fn eval_blind(&self) -> Robj {
+    fn eval_blind(&self) -> Robj {
         let res = self.eval();
         if let Ok(robj) = res {
             robj
@@ -651,6 +715,8 @@ impl Robj {
     }
 }
 
+impl Eval for Robj {}
+
 /// Generic access to typed slices in an Robj.
 pub trait AsTypedSlice<'a, T>
 where
@@ -715,7 +781,7 @@ make_typed_slice!(Rstr, STRING_PTR, STRSXP);
 
 /// These are helper functions which give access to common properties of R objects.
 #[allow(non_snake_case)]
-impl Robj {
+pub trait Attributes: Types + Length {
     /// Get a specific attribute as a borrowed robj if it exists.
     /// ```
     /// use extendr_api::prelude::*;
@@ -726,7 +792,7 @@ impl Robj {
     ///    assert_eq!(robj.get_attrib(sym!(xyz)), Some(r!(1)));
     /// }
     /// ```
-    pub fn get_attrib<'a, N>(&self, name: N) -> Option<Robj>
+    fn get_attrib<'a, N>(&self, name: N) -> Option<Robj>
     where
         Self: 'a,
         Robj: From<N> + 'a,
@@ -756,7 +822,7 @@ impl Robj {
     ///    assert_eq!(robj.get_attrib(sym!(xyz)), Some(r!(1)));
     /// }
     /// ```
-    pub fn set_attrib<N, V>(&self, name: N, value: V) -> Result<Robj>
+    fn set_attrib<N, V>(&self, name: N, value: V) -> Result<Robj>
     where
         N: Into<Robj>,
         V: Into<Robj>,
@@ -764,9 +830,10 @@ impl Robj {
         let name = name.into();
         let value = value.into();
         unsafe {
+            let sexp = self.get();
             single_threaded(|| {
-                catch_r_error(|| Rf_setAttrib(self.get(), name.get(), value.get()))
-                    .map(|_| self.clone())
+                catch_r_error(|| Rf_setAttrib(sexp, name.get(), value.get()))
+                    .map(|_| Robj::from_sexp(sexp))
             })
         }
     }
@@ -780,7 +847,7 @@ impl Robj {
     ///    assert_eq!(names, vec!["a", "b", "c"]);
     /// }
     /// ```
-    pub fn names(&self) -> Option<StrIter> {
+    fn names(&self) -> Option<StrIter> {
         if let Some(names) = self.get_attrib(wrapper::symbol::names_symbol()) {
             names.as_str_iter()
         } else {
@@ -800,7 +867,7 @@ impl Robj {
     ///     assert_eq!(r!([1, 2, 3]).set_names(&["a", "b"]), Err(Error::NamesLengthMismatch(r!(["a", "b"]))));
     /// }
     /// ```
-    pub fn set_names<T>(&mut self, names: T) -> Result<Robj>
+    fn set_names<T>(&mut self, names: T) -> Result<Robj>
     where
         T: IntoIterator,
         T::IntoIter: ExactSizeIterator,
@@ -827,7 +894,7 @@ impl Robj {
     ///    assert_eq!(dim, vec![2, 2]);
     /// }
     /// ```
-    pub fn dim(&self) -> Option<Integers> {
+    fn dim(&self) -> Option<Integers> {
         if let Some(dim) = self.get_attrib(wrapper::symbol::dim_symbol()) {
             dim.as_integers()
         } else {
@@ -844,7 +911,7 @@ impl Robj {
     ///    assert_eq!(names, vec![r!(["x", "y"]), r!(["a", "b"])]);
     /// }
     /// ```
-    pub fn dimnames(&self) -> Option<ListIter> {
+    fn dimnames(&self) -> Option<ListIter> {
         if let Some(names) = self.get_attrib(wrapper::symbol::dimnames_symbol()) {
             names.as_list().map(|v| v.values())
         } else {
@@ -861,7 +928,7 @@ impl Robj {
     ///    assert_eq!(class, ["formula"]);
     /// }
     /// ```
-    pub fn class(&self) -> Option<StrIter> {
+    fn class(&self) -> Option<StrIter> {
         if let Some(class) = self.get_attrib(wrapper::symbol::class_symbol()) {
             class.as_str_iter()
         } else {
@@ -881,7 +948,7 @@ impl Robj {
     ///     assert_eq!(obj.inherits("a"), true);
     /// }
     /// ```
-    pub fn set_class<T>(&self, class: T) -> Result<Robj>
+    fn set_class<T>(&self, class: T) -> Result<Robj>
     where
         T: IntoIterator,
         T::IntoIter: ExactSizeIterator,
@@ -899,7 +966,7 @@ impl Robj {
     ///    assert_eq!(formula.inherits("formula"), true);
     /// }
     /// ```
-    pub fn inherits(&self, classname: &str) -> bool {
+    fn inherits(&self, classname: &str) -> bool {
         if let Some(mut iter) = self.class() {
             iter.any(|n| n == classname)
         } else {
@@ -916,7 +983,7 @@ impl Robj {
     ///    assert_eq!(levels, vec!["abcd", "def", "fg"]);
     /// }
     /// ```
-    pub fn levels(&self) -> Option<StrIter> {
+    fn levels(&self) -> Option<StrIter> {
         if let Some(levels) = self.get_attrib(wrapper::symbol::levels_symbol()) {
             levels.as_str_iter()
         } else {
@@ -925,6 +992,8 @@ impl Robj {
     }
 }
 
+impl Attributes for Robj {}
+
 #[doc(hidden)]
 pub unsafe fn new_owned(sexp: SEXP) -> Robj {
     single_threaded(|| {
diff --git a/extendr-api/src/robj/operators.rs b/extendr-api/src/robj/operators.rs
index 568637460..c2c06e0aa 100644
--- a/extendr-api/src/robj/operators.rs
+++ b/extendr-api/src/robj/operators.rs
@@ -5,7 +5,7 @@ use std::ops::{Add, Div, Mul, Sub};
 ///////////////////////////////////////////////////////////////
 /// The following impls add operators to Robj.
 ///
-impl Robj {
+pub trait Operators: Rinternals {
     /// Do the equivalent of x$y
     /// ```
     /// use extendr_api::prelude::*;
@@ -16,12 +16,12 @@ impl Robj {
     /// assert_eq!(env.dollar("b").unwrap(), r!(2));
     /// }
     /// ```
-    pub fn dollar<T>(&self, symbol: T) -> Result<Robj>
+    fn dollar<T>(&self, symbol: T) -> Result<Robj>
     where
         T: AsRef<str>,
     {
         let symbol: Symbol = Symbol::from_string(symbol.as_ref());
-        call!("`$`", self, symbol)
+        call!("`$`", self.as_robj(), symbol)
     }
 
     /// Do the equivalent of `x[y]`
@@ -33,11 +33,11 @@ impl Robj {
     /// assert_eq!(vec.slice(2..=3).unwrap(), r!([20, 30]));
     /// }
     /// ```
-    pub fn slice<T>(&self, rhs: T) -> Result<Robj>
+    fn slice<T>(&self, rhs: T) -> Result<Robj>
     where
         T: Into<Robj>,
     {
-        call!("`[`", self, rhs.into())
+        call!("`[`", self.as_robj(), rhs.into())
     }
 
     /// Do the equivalent of `x[[y]]`
@@ -49,11 +49,11 @@ impl Robj {
     /// assert_eq!(vec.index(2..=3).is_err(), true);
     /// }
     /// ```
-    pub fn index<T>(&self, rhs: T) -> Result<Robj>
+    fn index<T>(&self, rhs: T) -> Result<Robj>
     where
         T: Into<Robj>,
     {
-        call!("`[[`", self, rhs.into())
+        call!("`[[`", self.as_robj(), rhs.into())
     }
 
     /// Do the equivalent of x ~ y
@@ -66,11 +66,11 @@ impl Robj {
     ///     assert_eq!(tilde.inherits("formula"), true);
     /// }
     /// ```
-    pub fn tilde<T>(&self, rhs: T) -> Result<Robj>
+    fn tilde<T>(&self, rhs: T) -> Result<Robj>
     where
         T: Into<Robj>,
     {
-        call!("`~`", self, rhs.into())
+        call!("`~`", self.as_robj(), rhs.into())
     }
 
     /// Do the equivalent of x :: y
@@ -83,11 +83,11 @@ impl Robj {
     ///     assert_eq!(base_list.is_function(), true);
     /// }
     /// ```
-    pub fn double_colon<T>(&self, rhs: T) -> Result<Robj>
+    fn double_colon<T>(&self, rhs: T) -> Result<Robj>
     where
         T: Into<Robj>,
     {
-        call!("`::`", self, rhs.into())
+        call!("`::`", self.as_robj(), rhs.into())
     }
 
     /// Do the equivalent of x(a, b, c)
@@ -99,14 +99,14 @@ impl Robj {
     ///     assert_eq!(function.call(pairlist!(a=1, b=2)).unwrap(), r!(3));
     /// }
     /// ```
-    pub fn call(&self, args: Pairlist) -> Result<Robj> {
+    fn call(&self, args: Pairlist) -> Result<Robj> {
         if self.is_function() {
             unsafe {
                 let call = Robj::from_sexp(Rf_lcons(self.get(), args.get()));
                 call.eval()
             }
         } else {
-            Err(Error::ExpectedFunction(self.clone()))
+            Err(Error::ExpectedFunction(self.as_robj().clone()))
         }
     }
 }
@@ -239,6 +239,8 @@ where
     }
 }
 
+impl Operators for Robj {}
+
 // Calls are still experimental.
 //
 // impl<Args> Fn(Args) for Robj
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 77badb07c..2784805b3 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -4,112 +4,112 @@ use std::os::raw;
 ///////////////////////////////////////////////////////////////
 /// The following impls wrap specific Rinternals.h functions.
 ///
-impl Robj {
+pub trait Rinternals: Types + Conversions {
     /// Return true if this is the null object.
-    pub fn is_null(&self) -> bool {
+    fn is_null(&self) -> bool {
         unsafe { Rf_isNull(self.get()) != 0 }
     }
 
     /// Return true if this is a symbol.
-    pub fn is_symbol(&self) -> bool {
+    fn is_symbol(&self) -> bool {
         unsafe { Rf_isSymbol(self.get()) != 0 }
     }
 
     /// Return true if this is a boolean (logical) vector
-    pub fn is_logical(&self) -> bool {
+    fn is_logical(&self) -> bool {
         unsafe { Rf_isLogical(self.get()) != 0 }
     }
 
     /// Return true if this is a real (f64) vector.
-    pub fn is_real(&self) -> bool {
+    fn is_real(&self) -> bool {
         unsafe { Rf_isReal(self.get()) != 0 }
     }
 
     /// Return true if this is a complex vector.
-    pub fn is_complex(&self) -> bool {
+    fn is_complex(&self) -> bool {
         unsafe { Rf_isComplex(self.get()) != 0 }
     }
 
     /// Return true if this is an expression.
-    pub fn is_expression(&self) -> bool {
+    fn is_expression(&self) -> bool {
         unsafe { Rf_isExpression(self.get()) != 0 }
     }
 
     /// Return true if this is an environment.
-    pub fn is_environment(&self) -> bool {
+    fn is_environment(&self) -> bool {
         unsafe { Rf_isEnvironment(self.get()) != 0 }
     }
 
     /// Return true if this is an environment.
-    pub fn is_promise(&self) -> bool {
+    fn is_promise(&self) -> bool {
         self.sexptype() == PROMSXP
     }
 
     /// Return true if this is a string.
-    pub fn is_string(&self) -> bool {
+    fn is_string(&self) -> bool {
         unsafe { Rf_isString(self.get()) != 0 }
     }
 
     /// Return true if this is an object (ie. has a class attribute).
-    pub fn is_object(&self) -> bool {
+    fn is_object(&self) -> bool {
         unsafe { Rf_isObject(self.get()) != 0 }
     }
 
     /// Return true if this is a S4 object.
-    pub fn is_s4(&self) -> bool {
+    fn is_s4(&self) -> bool {
         unsafe { Rf_isS4(self.get()) != 0 }
     }
 
     /// Return true if this is an expression.
-    pub fn is_external_pointer(&self) -> bool {
+    fn is_external_pointer(&self) -> bool {
         self.rtype() == RType::ExternalPtr
     }
 
     /// Get the source ref.
-    pub fn get_current_srcref(val: i32) -> Robj {
+    fn get_current_srcref(val: i32) -> Robj {
         unsafe { Robj::from_sexp(R_GetCurrentSrcref(val as raw::c_int)) }
     }
 
     /// Get the source filename.
-    pub fn get_src_filename(&self) -> Robj {
+    fn get_src_filename(&self) -> Robj {
         unsafe { Robj::from_sexp(R_GetSrcFilename(self.get())) }
     }
 
     /// Convert to a string vector.
-    pub fn as_character_vector(&self) -> Robj {
+    fn as_character_vector(&self) -> Robj {
         unsafe { Robj::from_sexp(Rf_asChar(self.get())) }
     }
 
     /// Convert to vectors of many kinds.
-    pub fn coerce_vector(&self, sexptype: u32) -> Robj {
+    fn coerce_vector(&self, sexptype: u32) -> Robj {
         single_threaded(|| unsafe {
             Robj::from_sexp(Rf_coerceVector(self.get(), sexptype as SEXPTYPE))
         })
     }
 
     /// Convert a pairlist (LISTSXP) to a vector list (VECSXP).
-    pub fn pair_to_vector_list(&self) -> Robj {
+    fn pair_to_vector_list(&self) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_PairToVectorList(self.get())) })
     }
 
     /// Convert a vector list (VECSXP) to a pair list (LISTSXP)
-    pub fn vector_to_pair_list(&self) -> Robj {
+    fn vector_to_pair_list(&self) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_VectorToPairList(self.get())) })
     }
 
     /// Convert a factor to a string vector.
-    pub fn as_character_factor(&self) -> Robj {
+    fn as_character_factor(&self) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_asCharacterFactor(self.get())) })
     }
 
     /// Allocate a matrix object.
-    pub fn alloc_matrix(sexptype: SEXPTYPE, rows: i32, cols: i32) -> Robj {
+    fn alloc_matrix(sexptype: SEXPTYPE, rows: i32, cols: i32) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_allocMatrix(sexptype, rows, cols)) })
     }
 
     /// Do a deep copy of this object.
     /// Note that clone() only adds a reference.
-    pub fn duplicate(&self) -> Self {
+    fn duplicate(&self) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_duplicate(self.get())) })
     }
 
@@ -128,7 +128,7 @@ impl Robj {
     ///    // assert!(base_env().find_function(sym!(qwertyuiop)).is_none());
     /// }
     /// ```
-    pub fn find_function<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
+    fn find_function<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
         let key: Symbol = key.try_into()?;
         if !self.is_environment() {
             return Err(Error::NotFound(key.into()));
@@ -150,7 +150,8 @@ impl Robj {
         //     }
         // }
         unsafe {
-            if let Ok(var) = catch_r_error(|| Rf_findFun(key.get(), self.get())) {
+            let sexp = self.get();
+            if let Ok(var) = catch_r_error(|| Rf_findFun(key.get(), sexp)) {
                 Ok(Robj::from_sexp(var))
             } else {
                 Err(Error::NotFound(key.into()))
@@ -176,7 +177,7 @@ impl Robj {
     ///    //assert_eq!(global_env().find_var(sym!(imnotasymbol)), None);
     /// }
     /// ```
-    pub fn find_var<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
+    fn find_var<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
         let key: Symbol = key.try_into()?;
         if !self.is_environment() {
             return Err(Error::NotFound(key.into()));
@@ -198,7 +199,8 @@ impl Robj {
         //     }
         // }
         unsafe {
-            if let Ok(var) = catch_r_error(|| Rf_findVar(key.get(), self.get())) {
+            let sexp = self.get();
+            if let Ok(var) = catch_r_error(|| Rf_findVar(key.get(), sexp)) {
                 if var != R_UnboundValue {
                     Ok(Robj::from_sexp(var))
                 } else {
@@ -219,27 +221,27 @@ impl Robj {
     ///    assert_eq!(iris_dataframe.is_frame(), true);
     /// }
     /// ```
-    pub fn eval_promise(&self) -> Result<Robj> {
+    fn eval_promise(&self) -> Result<Robj> {
         if self.is_promise() {
             self.as_promise().unwrap().eval()
         } else {
-            Ok(self.into())
+            Ok(self.as_robj().clone())
         }
     }
 
     /// Number of columns of a matrix
-    pub fn ncols(&self) -> usize {
+    fn ncols(&self) -> usize {
         unsafe { Rf_ncols(self.get()) as usize }
     }
 
     /// Number of rows of a matrix
-    pub fn nrows(&self) -> usize {
+    fn nrows(&self) -> usize {
         unsafe { Rf_nrows(self.get()) as usize }
     }
 
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
-    pub unsafe fn make_external_ptr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
+    unsafe fn make_external_ptr<T>(p: *mut T, tag: Robj, prot: Robj) -> Robj {
         Robj::from_sexp(single_threaded(|| {
             R_MakeExternalPtr(p as *mut ::std::os::raw::c_void, tag.get(), prot.get())
         }))
@@ -247,160 +249,160 @@ impl Robj {
 
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
-    pub unsafe fn external_ptr_addr<T>(&self) -> *mut T {
+    unsafe fn external_ptr_addr<T>(&self) -> *mut T {
         R_ExternalPtrAddr(self.get()) as *mut T
     }
 
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
-    pub unsafe fn external_ptr_tag(&self) -> Self {
+    unsafe fn external_ptr_tag(&self) -> Robj {
         Robj::from_sexp(R_ExternalPtrTag(self.get()))
     }
 
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
-    pub unsafe fn external_ptr_protected(&self) -> Self {
+    unsafe fn external_ptr_protected(&self) -> Robj {
         Robj::from_sexp(R_ExternalPtrProtected(self.get()))
     }
 
     #[doc(hidden)]
-    pub unsafe fn register_c_finalizer(&self, func: R_CFinalizer_t) {
+    unsafe fn register_c_finalizer(&self, func: R_CFinalizer_t) {
         single_threaded(|| R_RegisterCFinalizer(self.get(), func));
     }
 
     /// Copy a vector and resize it.
     /// See. <https://github.com/hadley/r-internals/blob/master/vectors.md>
-    pub fn xlengthgets(&self, new_len: usize) -> Result<Robj> {
+    fn xlengthgets(&self, new_len: usize) -> Result<Robj> {
         unsafe {
             if self.is_vector() {
                 Ok(single_threaded(|| {
                     Robj::from_sexp(Rf_xlengthgets(self.get(), new_len as R_xlen_t))
                 }))
             } else {
-                Err(Error::ExpectedVector(self.clone()))
+                Err(Error::ExpectedVector(self.as_robj().clone()))
             }
         }
     }
 
     /// Allocated an owned object of a certain type.
-    pub fn alloc_vector(sexptype: u32, len: usize) -> Robj {
+    fn alloc_vector(sexptype: u32, len: usize) -> Robj {
         single_threaded(|| unsafe { Robj::from_sexp(Rf_allocVector(sexptype, len as R_xlen_t)) })
     }
 
     /// Return true if two arrays have identical dims.
-    pub fn conformable(a: &Robj, b: &Robj) -> bool {
+    fn conformable(a: &Robj, b: &Robj) -> bool {
         single_threaded(|| unsafe { Rf_conformable(a.get(), b.get()) != 0 })
     }
 
     /// Return true if this is an array.
-    pub fn is_array(&self) -> bool {
+    fn is_array(&self) -> bool {
         unsafe { Rf_isArray(self.get()) != 0 }
     }
 
     /// Return true if this is factor.
-    pub fn is_factor(&self) -> bool {
+    fn is_factor(&self) -> bool {
         unsafe { Rf_isFactor(self.get()) != 0 }
     }
 
     /// Return true if this is a data frame.
-    pub fn is_frame(&self) -> bool {
+    fn is_frame(&self) -> bool {
         unsafe { Rf_isFrame(self.get()) != 0 }
     }
 
     /// Return true if this is a function or a primitive (CLOSXP, BUILTINSXP or SPECIALSXP)
-    pub fn is_function(&self) -> bool {
+    fn is_function(&self) -> bool {
         unsafe { Rf_isFunction(self.get()) != 0 }
     }
 
     /// Return true if this is an integer vector (INTSXP) but not a factor.
-    pub fn is_integer(&self) -> bool {
+    fn is_integer(&self) -> bool {
         unsafe { Rf_isInteger(self.get()) != 0 }
     }
 
     /// Return true if this is a language object (LANGSXP).
-    pub fn is_language(&self) -> bool {
+    fn is_language(&self) -> bool {
         unsafe { Rf_isLanguage(self.get()) != 0 }
     }
 
     /// Return true if this is NILSXP or LISTSXP.
-    pub fn is_pairlist(&self) -> bool {
+    fn is_pairlist(&self) -> bool {
         unsafe { Rf_isList(self.get()) != 0 }
     }
 
     /// Return true if this is a matrix.
-    pub fn is_matrix(&self) -> bool {
+    fn is_matrix(&self) -> bool {
         unsafe { Rf_isMatrix(self.get()) != 0 }
     }
 
     /// Return true if this is NILSXP or VECSXP.
-    pub fn is_list(&self) -> bool {
+    fn is_list(&self) -> bool {
         unsafe { Rf_isNewList(self.get()) != 0 }
     }
 
     /// Return true if this is INTSXP, LGLSXP or REALSXP but not a factor.
-    pub fn is_number(&self) -> bool {
+    fn is_number(&self) -> bool {
         unsafe { Rf_isNumber(self.get()) != 0 }
     }
 
     /// Return true if this is a primitive function BUILTINSXP, SPECIALSXP.
-    pub fn is_primitive(&self) -> bool {
+    fn is_primitive(&self) -> bool {
         unsafe { Rf_isPrimitive(self.get()) != 0 }
     }
 
     /// Return true if this is a time series vector (see tsp).
-    pub fn is_ts(&self) -> bool {
+    fn is_ts(&self) -> bool {
         unsafe { Rf_isTs(self.get()) != 0 }
     }
 
     /// Return true if this is a user defined binop.
-    pub fn is_user_binop(&self) -> bool {
+    fn is_user_binop(&self) -> bool {
         unsafe { Rf_isUserBinop(self.get()) != 0 }
     }
 
     /// Return true if this is a valid string.
-    pub fn is_valid_string(&self) -> bool {
+    fn is_valid_string(&self) -> bool {
         unsafe { Rf_isValidString(self.get()) != 0 }
     }
 
     /// Return true if this is a valid string.
-    pub fn is_valid_string_f(&self) -> bool {
+    fn is_valid_string_f(&self) -> bool {
         unsafe { Rf_isValidStringF(self.get()) != 0 }
     }
 
     /// Return true if this is a vector.
-    pub fn is_vector(&self) -> bool {
+    fn is_vector(&self) -> bool {
         unsafe { Rf_isVector(self.get()) != 0 }
     }
 
     /// Return true if this is an atomic vector.
-    pub fn is_vector_atomic(&self) -> bool {
+    fn is_vector_atomic(&self) -> bool {
         unsafe { Rf_isVectorAtomic(self.get()) != 0 }
     }
 
     /// Return true if this is a vector list.
-    pub fn is_vector_list(&self) -> bool {
+    fn is_vector_list(&self) -> bool {
         unsafe { Rf_isVectorList(self.get()) != 0 }
     }
 
     /// Return true if this is can be made into a vector.
-    pub fn is_vectorizable(&self) -> bool {
+    fn is_vectorizable(&self) -> bool {
         unsafe { Rf_isVectorizable(self.get()) != 0 }
     }
 
     /// Return true if this is RAWSXP.
-    pub fn is_raw(&self) -> bool {
+    fn is_raw(&self) -> bool {
         self.rtype() == RType::Raw
     }
 
     /// Return true if this is CHARSXP.
-    pub fn is_char(&self) -> bool {
+    fn is_char(&self) -> bool {
         self.rtype() == RType::Rstr
     }
 
     /// Check an external pointer tag.
     /// This is used to wrap R objects.
     #[doc(hidden)]
-    pub fn check_external_ptr(&self, expected_tag: &str) -> bool {
+    fn check_external_ptr(&self, expected_tag: &str) -> bool {
         if self.sexptype() == libR_sys::EXTPTRSXP {
             let tag = unsafe { self.external_ptr_tag() };
             if tag.as_str() == Some(expected_tag) {
@@ -410,63 +412,65 @@ impl Robj {
         false
     }
 
-    pub fn is_missing_arg(&self) -> bool {
+    fn is_missing_arg(&self) -> bool {
         unsafe { self.get() == R_MissingArg }
     }
 
-    pub fn is_unbound_value(&self) -> bool {
+    fn is_unbound_value(&self) -> bool {
         unsafe { self.get() == R_UnboundValue }
     }
 
-    pub fn is_package_env(&self) -> bool {
+    fn is_package_env(&self) -> bool {
         unsafe { R_IsPackageEnv(self.get()) != 0 }
     }
 
-    pub fn package_env_name(&self) -> Robj {
+    fn package_env_name(&self) -> Robj {
         unsafe { Robj::from_sexp(R_PackageEnvName(self.get())) }
     }
 
-    pub fn is_namespace_env(&self) -> bool {
+    fn is_namespace_env(&self) -> bool {
         unsafe { R_IsNamespaceEnv(self.get()) != 0 }
     }
 
-    pub fn namespace_env_spec(&self) -> Robj {
+    fn namespace_env_spec(&self) -> Robj {
         unsafe { Robj::from_sexp(R_NamespaceEnvSpec(self.get())) }
     }
 
     /// Returns `true` if this is an ALTREP object.
-    pub fn is_altrep(&self) -> bool {
+    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 {
+    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 {
+    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 {
+    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 {
+    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 {
+    fn is_altstring(&self) -> bool {
         unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == STRSXP as i32 }
     }
 
     /// Generate a text representation of this object.
-    pub fn deparse(&self) -> Result<Robj> {
+    fn deparse(&self) -> Result<Robj> {
         use crate as extendr_api;
-        call!("deparse", self)
+        call!("deparse", self.as_robj())
     }
 }
+
+impl Rinternals for Robj {}
diff --git a/extendr-api/src/serializer.rs b/extendr-api/src/serializer.rs
index c4781060a..203f1fda4 100644
--- a/extendr-api/src/serializer.rs
+++ b/extendr-api/src/serializer.rs
@@ -1,8 +1,10 @@
 //! See https://serde.rs/impl-serializer.html
 
 use crate::error::{Error, Result};
+use crate::robj::{AsStrIter, GetSexp, Length, Rinternals, Types};
 use crate::{
-    Doubles, Environment, Function, Integers, Language, Pairlist, Promise, Raw, Rstr, Symbol,
+    Doubles, Environment, Expression, Function, Integers, Language, Pairlist, Primitive, Promise,
+    Raw, Rstr, Symbol, S4,
 };
 use crate::{List, Rany, Robj};
 use serde::{ser, Serialize};
@@ -511,6 +513,36 @@ impl ser::Serialize for Symbol {
     }
 }
 
+impl ser::Serialize for Primitive {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        let string = self.deparse().unwrap();
+        string.serialize(serializer)
+    }
+}
+
+impl ser::Serialize for Expression {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        let string = self.deparse().unwrap();
+        string.serialize(serializer)
+    }
+}
+
+impl ser::Serialize for S4 {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        let string = self.deparse().unwrap();
+        string.serialize(serializer)
+    }
+}
+
 impl ser::Serialize for Pairlist {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
@@ -533,16 +565,6 @@ impl ser::Serialize for Function {
     {
         let string = self.deparse().unwrap();
         string.serialize(serializer)
-        // if let Some(s) = string.as_str() {
-        //     serializer.serialize_str(s)
-        // } else {
-        //     use serde::ser::SerializeSeq;
-        //     let mut s = serializer.serialize_seq(Some(self.len()))?;
-        //     for v in string.as_str_iter().unwrap() {
-        //         s.serialize_element(v)?;
-        //     }
-        //     s.end()
-        // }
     }
 }
 
@@ -583,7 +605,7 @@ impl ser::Serialize for Integers {
         } else {
             use serde::ser::SerializeSeq;
             let mut s = serializer.serialize_seq(Some(self.len()))?;
-            for v in self.as_integer_slice().unwrap() {
+            for v in self.as_robj().as_integer_slice().unwrap() {
                 // TODO: use get_range when available.
                 s.serialize_element(v)?;
             }
@@ -611,7 +633,7 @@ impl ser::Serialize for Doubles {
         } else {
             use serde::ser::SerializeSeq;
             let mut s = serializer.serialize_seq(Some(self.len()))?;
-            for v in self.as_real_slice().unwrap() {
+            for v in self.as_robj().as_real_slice().unwrap() {
                 // TODO: use get_range when available.
                 s.serialize_element(v)?;
             }
@@ -634,7 +656,7 @@ impl ser::Serialize for Raw {
     where
         S: ser::Serializer,
     {
-        serializer.serialize_bytes(self.as_raw_slice().unwrap())
+        serializer.serialize_bytes(self.as_robj().as_raw_slice().unwrap())
     }
 }
 
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
index f57516758..76991752b 100644
--- a/extendr-api/src/wrapper/list.rs
+++ b/extendr-api/src/wrapper/list.rs
@@ -1,4 +1,5 @@
 use std::iter::FromIterator;
+use crate::robj::Attributes;
 
 use super::*;
 
@@ -62,7 +63,11 @@ impl List {
             values.push(pair.value());
         }
         let mut res = List::from_values(values);
-        res.set_names(names).unwrap().as_list().unwrap()
+        res.as_robj_mut()
+            .set_names(names)
+            .unwrap()
+            .as_list()
+            .unwrap()
     }
 
     /// Wrapper for creating a list (VECSXP) object from an existing `HashMap`.
@@ -388,3 +393,5 @@ impl<T: Into<Robj>> FromIterator<T> for List {
         })
     }
 }
+
+impl Attributes for List {}
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index 372758a6b..68e26ad7f 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -119,7 +119,7 @@ macro_rules! gen_vector_wrapper_impl {
                 #[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()
+                    self.as_robj().as_typed_slice().unwrap().iter().cloned()
                 }
             }
 
@@ -127,7 +127,7 @@ macro_rules! gen_vector_wrapper_impl {
                 #[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()
+                    self.as_robj_mut().as_typed_slice_mut().unwrap().iter_mut()
                 }
             }
         }
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
index bf6f63321..4b99a73d5 100644
--- a/extendr-api/src/wrapper/matrix.rs
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -1,6 +1,7 @@
 //! Wrappers for matrices with deferred arithmetic.
 
 use super::*;
+use crate::robj::GetSexp;
 use std::ops::{Index, IndexMut};
 
 /// Wrapper for creating and using matrices and arrays.
@@ -279,29 +280,31 @@ impl<T, D> From<RArray<T, D>> for Robj {
     }
 }
 
-impl Robj {
-    pub fn as_column<'a, E: 'a>(&self) -> Option<RColumn<E>>
+pub trait MatrixConversions: GetSexp {
+    fn as_column<'a, E: 'a>(&self) -> Option<RColumn<E>>
     where
-        Self: AsTypedSlice<'a, E>,
+        Robj: AsTypedSlice<'a, E>,
     {
-        <RColumn<E>>::try_from(self.clone()).ok()
+        <RColumn<E>>::try_from(self.as_robj().clone()).ok()
     }
 
-    pub fn as_matrix<'a, E: 'a>(&self) -> Option<RMatrix<E>>
+    fn as_matrix<'a, E: 'a>(&self) -> Option<RMatrix<E>>
     where
-        Self: AsTypedSlice<'a, E>,
+        Robj: AsTypedSlice<'a, E>,
     {
-        <RMatrix<E>>::try_from(self.clone()).ok()
+        <RMatrix<E>>::try_from(self.as_robj().clone()).ok()
     }
 
-    pub fn as_matrix3d<'a, E: 'a>(&self) -> Option<RMatrix3D<E>>
+    fn as_matrix3d<'a, E: 'a>(&self) -> Option<RMatrix3D<E>>
     where
-        Self: AsTypedSlice<'a, E>,
+        Robj: AsTypedSlice<'a, E>,
     {
-        <RMatrix3D<E>>::try_from(self.clone()).ok()
+        <RMatrix3D<E>>::try_from(self.as_robj().clone()).ok()
     }
 }
 
+impl MatrixConversions for Robj {}
+
 impl<T> Index<[usize; 2]> for RArray<T, [usize; 2]> {
     type Output = T;
 
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index addde6273..9e3498b55 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -1,6 +1,7 @@
 //! Wrappers are lightweight proxies for references to R datatypes.
 //! They do not contain an Robj (see array.rs for an example of this).
 
+use crate::robj::{GetSexp, Rinternals};
 use crate::*;
 use libR_sys::*;
 
@@ -38,7 +39,7 @@ pub use function::Function;
 pub use integers::Integers;
 pub use lang::Language;
 pub use list::{FromList, List, ListIter};
-pub use matrix::{RArray, RColumn, RMatrix, RMatrix3D};
+pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D};
 pub use nullable::Nullable;
 pub use pairlist::{Pairlist, PairlistIter};
 pub use primitive::Primitive;
@@ -104,21 +105,39 @@ macro_rules! make_conversions {
             }
         }
 
-        impl Deref for $typename {
-            type Target = Robj;
+        impl GetSexp for $typename {
+            unsafe fn get(&self) -> SEXP {
+                self.robj.get()
+            }
 
-            /// Make a wrapper behave like an Robj.
-            fn deref(&self) -> &Self::Target {
+            fn as_robj(&self) -> &Robj {
                 &self.robj
             }
-        }
 
-        impl DerefMut for $typename {
-            /// Make a wrapper behave like a writable Robj.
-            fn deref_mut(&mut self) -> &mut Self::Target {
+            fn as_robj_mut(&mut self) -> &mut Robj {
                 &mut self.robj
             }
         }
+
+        // These traits all derive from GetSexp
+
+        /// len() and is_empty()
+        impl Length for $typename {}
+
+        /// rtype() and rany()
+        impl Types for $typename {}
+
+        /// as_*()
+        impl Conversions for $typename {}
+
+        /// find_var() etc.
+        impl Rinternals for $typename {}
+
+        /// as_typed_slice_raw() etc.
+        impl Slices for $typename {}
+
+        /// dollar() etc.
+        impl Operators for $typename {}
     };
 }
 
@@ -179,7 +198,15 @@ make_conversions!(Doubles, ExpectedReal, is_real, "Not a floating point type");
 
 make_conversions!(Strings, ExpectedString, is_string, "Not a string vector");
 
-impl Robj {
+// impl Deref for Integers {
+//     type Target = [Rint];
+
+//     fn deref(&self) -> &Self::Target {
+//         unsafe { self.as_typed_slice_raw() }
+//     }
+// }
+
+pub trait Conversions: GetSexp {
     /// Convert a symbol object to a Symbol wrapper.
     /// ```
     /// use extendr_api::prelude::*;
@@ -188,8 +215,8 @@ impl Robj {
     ///     assert_eq!(fred.as_symbol(), Some(Symbol::from_string("fred")));
     /// }
     /// ```
-    pub fn as_symbol(&self) -> Option<Symbol> {
-        Symbol::try_from(self.clone()).ok()
+    fn as_symbol(&self) -> Option<Symbol> {
+        Symbol::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a CHARSXP object to a Rstr wrapper.
@@ -200,8 +227,8 @@ impl Robj {
     ///     assert_eq!(fred.as_char(), Some(Rstr::from_string("fred")));
     /// }
     /// ```
-    pub fn as_char(&self) -> Option<Rstr> {
-        Rstr::try_from(self.clone()).ok()
+    fn as_char(&self) -> Option<Rstr> {
+        Rstr::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a raw object to a Rstr wrapper.
@@ -213,8 +240,8 @@ impl Robj {
     ///     assert_eq!(bytes.as_raw(), Some(Raw::from_bytes(&[1, 2, 3])));
     /// }
     /// ```
-    pub fn as_raw(&self) -> Option<Raw> {
-        Raw::try_from(self.clone()).ok()
+    fn as_raw(&self) -> Option<Raw> {
+        Raw::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a language object to a Language wrapper.
@@ -227,8 +254,8 @@ impl Robj {
     ///     assert_eq!(format!("{:?}", call_to_xyz), r#"r!(Language::from_values([sym!(xyz), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_language(&self) -> Option<Language> {
-        Language::try_from(self.clone()).ok()
+    fn as_language(&self) -> Option<Language> {
+        Language::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a pair list object (LISTSXP) to a Pairlist wrapper.
@@ -241,8 +268,8 @@ impl Robj {
     ///     assert_eq!(robj.as_pairlist().unwrap(), pairlist);
     /// }
     /// ```
-    pub fn as_pairlist(&self) -> Option<Pairlist> {
-        Pairlist::try_from(self.clone()).ok()
+    fn as_pairlist(&self) -> Option<Pairlist> {
+        Pairlist::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a list object (VECSXP) to a List wrapper.
@@ -254,8 +281,8 @@ impl Robj {
     ///     assert_eq!(format!("{:?}", list), r#"r!(List::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_list(&self) -> Option<List> {
-        List::try_from(self.clone()).ok()
+    fn as_list(&self) -> Option<List> {
+        List::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert an expression object (EXPRSXP) to a Expr wrapper.
@@ -268,8 +295,8 @@ impl Robj {
     ///     assert_eq!(format!("{:?}", expr), r#"r!(Expression::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_expression(&self) -> Option<Expression> {
-        Expression::try_from(self.clone()).ok()
+    fn as_expression(&self) -> Option<Expression> {
+        Expression::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert an environment object (ENVSXP) to a Env wrapper.
@@ -284,8 +311,8 @@ impl Robj {
     ///     assert_eq!(env2.len(), 100);
     /// }
     /// ```
-    pub fn as_environment(&self) -> Option<Environment> {
-        Environment::try_from(self.clone()).ok()
+    fn as_environment(&self) -> Option<Environment> {
+        Environment::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert a function object (CLOSXP) to a Function wrapper.
@@ -296,16 +323,18 @@ impl Robj {
     ///     println!("{:?}", func.as_function());
     /// }
     /// ```
-    pub fn as_function(&self) -> Option<Function> {
-        Function::try_from(self.clone()).ok()
+    fn as_function(&self) -> Option<Function> {
+        Function::try_from(self.as_robj().clone()).ok()
     }
 
     /// Get a wrapper for a promise.
-    pub fn as_promise(&self) -> Option<Promise> {
-        Promise::try_from(self.clone()).ok()
+    fn as_promise(&self) -> Option<Promise> {
+        Promise::try_from(self.as_robj().clone()).ok()
     }
 }
 
+impl Conversions for Robj {}
+
 pub trait SymPair {
     fn sym_pair(self) -> (Option<Robj>, Robj);
 }


Test patch:
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index c897b885b..f7f9a862e 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -35,7 +35,7 @@ fn test_altinteger() {
 
         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]);
+        assert_eq!(obj.as_robj().as_integer_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
 
         // index 5 is missing
         let mystate_w_missing = MyCompactIntRange { start: 0, len: 10, step: 1, missing_index: 5 };
@@ -89,7 +89,7 @@ fn test_altreal() {
 
         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]);
+        assert_eq!(obj.as_robj().as_real_slice().unwrap(), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
 
         // index 5 is missing
         let mystate_w_missing = MyCompactRealRange { start: 0.0, len: 10, step: 1.0, missing_index: 5 };
@@ -138,7 +138,7 @@ fn test_altlogical() {
 
         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]);
+        assert_eq!(obj.as_robj().as_logical_slice().unwrap(), [FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE]);
     }
 }
 
@@ -170,7 +170,7 @@ fn test_altraw() {
         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]);
+        assert_eq!(obj.as_robj().as_raw_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
     }
 }
 
diff --git a/extendr-api/tests/serializer_tests.rs b/extendr-api/tests/serializer_tests.rs
index d8a73482f..0b03c8015 100644
--- a/extendr-api/tests/serializer_tests.rs
+++ b/extendr-api/tests/serializer_tests.rs
@@ -85,7 +85,7 @@ mod test {
                 // s4: S4,
             }
 
-            let test = Test {
+            let _test = Test {
                 null: r!(()),
                 symbol: sym!("xyz").try_into()?,
                 pairlist: pairlist!(x=1),
@@ -112,7 +112,7 @@ mod test {
                 // s4: S4,
             };
 
-            let expected = list!(int=1, seq=list!(null = (), symbol = (), pairlist = (), function = (), rstr = (), integer = (), real = (), list = (), raw = ()));
+            let _expected = list!(int=1, seq=list!(null = (), symbol = (), pairlist = (), function = (), rstr = (), integer = (), real = (), list = (), raw = ()));
             // assert_eq!(to_robj(&test).unwrap(), Robj::from(expected));
         }
     }