Repository: extendr/extendr
Pull request #345 (2021-12-25 22:07:02)

Refactoring
This PR moves several things forward.

Debug is now supported directly for wrapper types.

Expression is renamed to Expressions.

Serialize now works for more types.

This seems like quite a big change, but it is all just refactoring moving towards our next formal release.

andy-thomason: ```
==> Running `brew cleanup checkbashisms`...
Error: The operation was canceled.
```

I'm guessing this is a transient failure on actions behalf, perhaps a network failure.

Ilia-Kosenkov: It seems you were unlucky and caught yet another transient error. I rescheduled tests, let's see if it works this time.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4267553c4..c92f1a63e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,14 @@
 
 ## extendr devel
 
+- Extra Serialize support.
+
+- Direct Debug support for wrappers.
+
+- Strings::elt returns Rstr.
+
+- Renamed RType to Rtype.
+
 - Removed Bool type.
 
 - Introduced scalars Rint, Rfloat and Rbool.
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index a538dc496..15ab910db 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -107,11 +107,11 @@ impl std::fmt::Display for Error {
             Error::ExpectedRstr(robj) => {
                 write!(f, "Expected Rstr got {:?}", robj.rtype())
             }
-            Error::ExpectedLogical(robj) => write!(f, "Expected Logical got {:?}", robj.rtype()),
-            Error::ExpectedInteger(robj) => write!(f, "Expected Integer got {:?}", robj.rtype()),
-            Error::ExpectedReal(robj) => write!(f, "Expected Real got {:?}", robj.rtype()),
-            Error::ExpectedComplex(robj) => write!(f, "Expected Complex got {:?}", robj.rtype()),
-            Error::ExpectedString(robj) => write!(f, "Expected String got {:?}", robj.rtype()),
+            Error::ExpectedLogical(robj) => write!(f, "Expected Logicals got {:?}", robj.rtype()),
+            Error::ExpectedInteger(robj) => write!(f, "Expected Integers got {:?}", robj.rtype()),
+            Error::ExpectedReal(robj) => write!(f, "Expected Doubles got {:?}", robj.rtype()),
+            Error::ExpectedComplex(robj) => write!(f, "Expected Complexes got {:?}", robj.rtype()),
+            Error::ExpectedString(robj) => write!(f, "Expected Strings got {:?}", robj.rtype()),
             Error::ExpectedDot(robj) => write!(f, "Expected Dot got {:?}", robj.rtype()),
             Error::ExpectedAny(robj) => write!(f, "Expected Any got {:?}", robj.rtype()),
             Error::ExpectedList(robj) => write!(f, "Expected List got {:?}", robj.rtype()),
diff --git a/extendr-api/src/functions.rs b/extendr-api/src/functions.rs
index 4d7a0c2ec..4f1d61578 100644
--- a/extendr-api/src/functions.rs
+++ b/extendr-api/src/functions.rs
@@ -203,10 +203,10 @@ pub fn blank_scalar_string() -> Robj {
 /// use extendr_api::prelude::*;
 /// test! {
 ///    let expr = parse("1 + 2").unwrap();
-///    assert!(expr.is_expression());
+///    assert!(expr.is_expressions());
 /// }
 /// ```
-pub fn parse(code: &str) -> Result<Robj> {
+pub fn parse(code: &str) -> Result<Expressions> {
     single_threaded(|| unsafe {
         use libR_sys::*;
         let mut status = 0_u32;
@@ -214,7 +214,7 @@ pub fn parse(code: &str) -> Result<Robj> {
         let codeobj: Robj = code.into();
         let parsed = Robj::from_sexp(R_ParseVector(codeobj.get(), -1, status_ptr, R_NilValue));
         match status {
-            1 => Ok(parsed),
+            1 => parsed.try_into(),
             _ => Err(Error::ParseError(code.into())),
         }
     })
@@ -233,7 +233,7 @@ pub fn eval_string(code: &str) -> Result<Robj> {
     single_threaded(|| {
         let expr = parse(code)?;
         let mut res = Robj::from(());
-        if let Some(expr) = expr.as_expression() {
+        if let Some(expr) = expr.as_expressions() {
             for lang in expr.values() {
                 res = lang.eval()?
             }
@@ -263,7 +263,7 @@ pub fn eval_string_with_params(code: &str, values: &[&Robj]) -> Result<Robj> {
 
         let expr = parse(code)?;
         let mut res = Robj::from(());
-        if let Some(expr) = expr.as_expression() {
+        if let Some(expr) = expr.as_expressions() {
             for lang in expr.values() {
                 res = lang.eval_with_env(&env)?
             }
diff --git a/extendr-api/src/iter.rs b/extendr-api/src/iter.rs
index 135ab38a5..3c208c96e 100644
--- a/extendr-api/src/iter.rs
+++ b/extendr-api/src/iter.rs
@@ -30,18 +30,18 @@ pub struct StrIter {
 
 impl Default for StrIter {
     fn default() -> Self {
-        StrIter::new()
+        StrIter::new(0)
     }
 }
 
 impl StrIter {
     /// Make an empty str iterator.
-    pub fn new() -> Self {
+    pub fn new(len: usize) -> Self {
         unsafe {
             Self {
                 vector: ().into(),
                 i: 0,
-                len: 0,
+                len,
                 levels: R_NilValue,
             }
         }
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 947048f10..6de270ad5 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -217,6 +217,7 @@
 //!     assert_eq!(slice.len(), 3);
 //! }
 //! ```
+//!
 
 #![doc(
     html_logo_url = "https://raw.githubusercontent.com/extendr/extendr/master/extendr-logo-256.png"
@@ -376,7 +377,7 @@ pub unsafe fn register_call_methods(info: *mut libR_sys::DllInfo, metadata: Meta
 
 /// Type of R objects used by [Robj::rtype].
 #[derive(Debug, PartialEq)]
-pub enum RType {
+pub enum Rtype {
     Null,        // NILSXP
     Symbol,      // SYMSXP
     Pairlist,    // LISTSXP
@@ -387,15 +388,15 @@ pub enum RType {
     Special,     // SPECIALSXP
     Builtin,     // BUILTINSXP
     Rstr,        // CHARSXP
-    Logical,     // LGLSXP
-    Integer,     // INTSXP
-    Real,        // REALSXP
-    Complex,     // CPLXSXP
-    String,      // STRSXP
+    Logicals,    // LGLSXP
+    Integers,    // INTSXP
+    Doubles,     // REALSXP
+    Complexes,   // CPLXSXP
+    Strings,     // STRSXP
     Dot,         // DOTSXP
     Any,         // ANYSXP
     List,        // VECSXP
-    Expression,  // EXPRSXP
+    Expressions, // EXPRSXP
     Bytecode,    // BCODESXP
     ExternalPtr, // EXTPTRSXP
     WeakRef,     // WEAKREFSXP
@@ -419,15 +420,15 @@ pub enum Rany<'a> {
     Special(&'a Primitive),       // SPECIALSXP
     Builtin(&'a Primitive),       // BUILTINSXP
     Rstr(&'a Rstr),               // CHARSXP
-    Logical(&'a Logicals),        // LGLSXP
-    Integer(&'a Integers),        // INTSXP
-    Real(&'a Doubles),            // REALSXP
-    Complex(&'a Complexes),       // CPLXSXP
-    String(&'a Robj),             // STRSXP
+    Logicals(&'a Logicals),       // LGLSXP
+    Integers(&'a Integers),       // INTSXP
+    Doubles(&'a Doubles),         // REALSXP
+    Complexes(&'a Robj),          // CPLXSXP
+    Strings(&'a Strings),         // STRSXP
     Dot(&'a Robj),                // DOTSXP
     Any(&'a Robj),                // ANYSXP
     List(&'a List),               // VECSXP
-    Expression(&'a Expression),   // EXPRSXP
+    Expressions(&'a Expressions), // EXPRSXP
     Bytecode(&'a Robj),           // BCODESXP
     ExternalPtr(&'a Robj),        // EXTPTRSXP
     WeakRef(&'a Robj),            // WEAKREFSXP
@@ -436,10 +437,10 @@ pub enum Rany<'a> {
     Unknown(&'a Robj),
 }
 
-/// Convert extendr's RType to R's SEXPTYPE.
+/// Convert extendr's Rtype to R's SEXPTYPE.
 /// Panics if the type is Unknown.
-pub fn rtype_to_sxp(rtype: RType) -> i32 {
-    use RType::*;
+pub fn rtype_to_sxp(rtype: Rtype) -> i32 {
+    use Rtype::*;
     (match rtype {
         Null => NILSXP,
         Symbol => SYMSXP,
@@ -451,27 +452,27 @@ pub fn rtype_to_sxp(rtype: RType) -> i32 {
         Special => SPECIALSXP,
         Builtin => BUILTINSXP,
         Rstr => CHARSXP,
-        Logical => LGLSXP,
-        Integer => INTSXP,
-        Real => REALSXP,
-        Complex => CPLXSXP,
-        String => STRSXP,
+        Logicals => LGLSXP,
+        Integers => INTSXP,
+        Doubles => REALSXP,
+        Complexes => CPLXSXP,
+        Strings => STRSXP,
         Dot => DOTSXP,
         Any => ANYSXP,
         List => VECSXP,
-        Expression => EXPRSXP,
+        Expressions => EXPRSXP,
         Bytecode => BCODESXP,
         ExternalPtr => EXTPTRSXP,
         WeakRef => WEAKREFSXP,
         Raw => RAWSXP,
         S4 => S4SXP,
-        Unknown => panic!("attempt to use Unknown RType"),
+        Unknown => panic!("attempt to use Unknown Rtype"),
     }) as i32
 }
 
-/// Convert R's SEXPTYPE to extendr's RType.
-pub fn sxp_to_rtype(sxptype: i32) -> RType {
-    use RType::*;
+/// Convert R's SEXPTYPE to extendr's Rtype.
+pub fn sxp_to_rtype(sxptype: i32) -> Rtype {
+    use Rtype::*;
     match sxptype as u32 {
         NILSXP => Null,
         SYMSXP => Symbol,
@@ -483,15 +484,15 @@ pub fn sxp_to_rtype(sxptype: i32) -> RType {
         SPECIALSXP => Special,
         BUILTINSXP => Builtin,
         CHARSXP => Rstr,
-        LGLSXP => Logical,
-        INTSXP => Integer,
-        REALSXP => Real,
-        CPLXSXP => Complex,
-        STRSXP => String,
+        LGLSXP => Logicals,
+        INTSXP => Integers,
+        REALSXP => Doubles,
+        CPLXSXP => Complexes,
+        STRSXP => Strings,
         DOTSXP => Dot,
         ANYSXP => Any,
         VECSXP => List,
-        EXPRSXP => Expression,
+        EXPRSXP => Expressions,
         BCODESXP => Bytecode,
         EXTPTRSXP => ExternalPtr,
         WEAKREFSXP => WeakRef,
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index 14aef6597..a04eb36b4 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -153,7 +153,7 @@ fn write_function_wrapper(
     let formal_args = func
         .args
         .iter()
-        .map(|arg| gen_formal_arg(arg))
+        .map(gen_formal_arg)
         .collect::<Vec<_>>()
         .join(", ");
 
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 184f8d86a..625b96190 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -4,7 +4,7 @@
 //! using deprecated features.
 
 pub use super::{
-    print_r_error, print_r_output, CanBeNA, FromRobj, RType, FALSE, NA_INTEGER, NA_LOGICAL,
+    print_r_error, print_r_output, CanBeNA, FromRobj, Rtype, FALSE, NA_INTEGER, NA_LOGICAL,
     NA_REAL, NA_STRING, NULL, TRUE,
 };
 
@@ -55,7 +55,7 @@ pub use super::thread_safety::{
 };
 
 pub use super::wrapper::{
-    Complexes, Doubles, EnvIter, Environment, Expression, ExternalPtr, FromList, Function,
+    Complexes, Doubles, EnvIter, Environment, Expressions, ExternalPtr, FromList, Function,
     Integers, Language, List, ListIter, Logicals, Nullable, Pairlist, Primitive, Promise, Raw,
     Rstr, Strings, Symbol,
 };
diff --git a/extendr-api/src/robj/debug.rs b/extendr-api/src/robj/debug.rs
new file mode 100644
index 000000000..7267d22fb
--- /dev/null
+++ b/extendr-api/src/robj/debug.rs
@@ -0,0 +1,188 @@
+use crate::wrapper::*;
+// use crate::robj::GetSexp;
+use crate::robj::Attributes;
+use crate::robj::Rany;
+use crate::robj::Rinternals;
+use crate::robj::Robj;
+use crate::robj::Types;
+
+impl std::fmt::Debug for Symbol {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.is_missing_arg() {
+            write!(f, "missing_arg()")
+        } else if self.is_unbound_value() {
+            write!(f, "unbound_value()")
+        } else {
+            write!(f, "sym!({})", self.as_symbol().unwrap().as_str())
+        }
+    }
+}
+
+/// Implement {:?} formatting.
+impl std::fmt::Debug for Robj {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self.as_any() {
+            Rany::Null(_) => write!(f, "()"),
+            Rany::Symbol(value) => value.fmt(f),
+            Rany::Pairlist(value) => value.fmt(f),
+            Rany::Function(value) => value.fmt(f),
+            Rany::Environment(value) => value.fmt(f),
+            Rany::Promise(value) => value.fmt(f),
+            Rany::Language(value) => value.fmt(f),
+            Rany::Special(value) => value.fmt(f),
+            Rany::Builtin(value) => value.fmt(f),
+            Rany::Rstr(value) => value.fmt(f),
+            Rany::Logicals(value) => value.fmt(f),
+            Rany::Integers(value) => value.fmt(f),
+            Rany::Doubles(value) => value.fmt(f),
+            Rany::Complexes(_complex) => {
+                todo!("implement Complexes")
+            }
+            Rany::Strings(value) => write!(f, "{:?}", value.as_slice()),
+            Rany::Dot(_dot) => write!(f, "Dot"),
+            Rany::Any(_any) => write!(f, "Any"),
+            Rany::List(value) => value.fmt(f),
+            Rany::Expressions(value) => value.fmt(f),
+            Rany::Bytecode(_bytecode) => write!(f, "Bytecode"),
+            Rany::ExternalPtr(_externalptr) => write!(f, "ExternalPtr"),
+            Rany::WeakRef(_weakref) => write!(f, "Weakref"),
+            Rany::Raw(value) => value.fmt(f),
+            Rany::S4(value) => value.fmt(f),
+            Rany::Unknown(_unknown) => write!(f, "Unknown"),
+        }?;
+        /*
+               match self.sexptype() {
+                   NILSXP => write!(f, "r!(NULL)"),
+                   SYMSXP => {
+                       if self.is_missing_arg() {
+                           write!(f, "missing_arg()")
+                       } else if self.is_unbound_value() {
+                           write!(f, "unbound_value()")
+                       } else {
+                           write!(f, "sym!({})", self.as_symbol().unwrap().as_str())
+                       }
+                   }
+                   LISTSXP => {
+                       let pairlist = self.as_pairlist().unwrap().iter();
+                       write!(f, "r!({:?})", pairlist)
+                   }
+                   CLOSXP => {
+                       let func = self.as_function().unwrap();
+                       let formals = func.formals();
+                       let body = func.body();
+                       let environment = func.environment();
+                       write!(
+                           f,
+                           "r!(Function::from_parts({:?}, {:?}, {:?}))",
+                           formals, body, environment
+                       )
+                   }
+                   ENVSXP => unsafe {
+                       let sexp = self.get();
+                       if sexp == R_GlobalEnv {
+                           write!(f, "global_env()")
+                       } else if sexp == R_BaseEnv {
+                           write!(f, "base_env()")
+                       } else if sexp == R_EmptyEnv {
+                           write!(f, "empty_env()")
+                       } else {
+                           write!(f, "r!(Environment::from_pairs(...))")
+                       }
+                   },
+                   PROMSXP => {
+                       let p = self.as_promise().unwrap();
+                       write!(
+                           f,
+                           "r!(Promise::from_parts({:?}, {:?}))",
+                           p.code(),
+                           p.environment()
+                       )
+                   }
+                   LANGSXP => write!(
+                       f,
+                       "r!(Language::from_values({:?}))",
+                       self.as_language().unwrap().values().collect::<Vec<_>>()
+                   ),
+                   SPECIALSXP => write!(f, "r!(Special())"),
+                   BUILTINSXP => write!(f, "r!(Builtin())"),
+                   CHARSXP => {
+                       let c = Rstr::try_from(self.clone()).unwrap();
+                       write!(f, "r!(Rstr::from_string({:?}))", c.as_str())
+                   }
+                   LGLSXP => {
+                       let slice = self.as_logical_slice().unwrap();
+                       if slice.len() == 1 {
+                           write!(f, "r!({:?})", slice[0])
+                       } else {
+                           write!(f, "r!({:?})", slice)
+                       }
+                   }
+                   INTSXP => {
+                       let slice = self.as_integer_slice().unwrap();
+                       if slice.len() == 1 {
+                           write!(f, "r!({:?})", slice[0])
+                       } else {
+                           write!(f, "r!({:?})", self.as_integer_slice().unwrap())
+                       }
+                   }
+                   REALSXP => {
+                       let slice = self.as_real_slice().unwrap();
+                       if slice.len() == 1 {
+                           write!(f, "r!({:?})", slice[0])
+                       } else {
+                           write!(f, "r!({:?})", slice)
+                       }
+                   }
+                   VECSXP => {
+                       let list = self.as_list().unwrap();
+                       if self.names().is_some() {
+                           write!(f, "r!(List::from_pairs({:?}))", list.iter())
+                       } else {
+                           write!(f, "r!(List::from_values({:?}))", list.values())
+                       }
+                   }
+                   EXPRSXP => write!(
+                       f,
+                       "r!(Expressions::from_values({:?}))",
+                       self.as_expressions().unwrap().values()
+                   ),
+                   WEAKREFSXP => write!(f, "r!(Weakref())"),
+                   // CPLXSXP => false,
+                   STRSXP => {
+                       write!(f, "r!([")?;
+                       let mut sep = "";
+                       for s in self.as_str_iter().unwrap() {
+                           // if s.is_na() {
+                           //     write!(f, "{}na_str()", sep)?;
+                           // } else {
+                           write!(f, "{}{:?}", sep, s)?;
+                           // }
+                           sep = ", ";
+                       }
+                       write!(f, "])")
+                   }
+                   DOTSXP => write!(f, "r!(Dot())"),
+                   ANYSXP => write!(f, "r!(Any())"),
+                   BCODESXP => write!(f, "r!(Bcode())"),
+                   EXTPTRSXP => {
+                       write!(f, "r!(ExternalPtr())")
+                   }
+                   RAWSXP => {
+                       write!(
+                           f,
+                           "r!(Raw::from_bytes({:?}))",
+                           self.as_raw().unwrap().as_slice()
+                       )
+                   }
+                   S4SXP => write!(f, "r!(S4())"),
+                   NEWSXP => write!(f, "r!(New())"),
+                   FREESXP => write!(f, "r!(Free())"),
+                   _ => write!(f, "??"),
+               }?;
+        */
+        if let Some(c) = self.class() {
+            write!(f, ".set_class({:?}", c)?;
+        }
+        Ok(())
+    }
+}
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index cac300054..4128db7ca 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -23,6 +23,7 @@ use std::ops::{Range, RangeInclusive};
 // deprecated
 mod from_robj;
 
+mod debug;
 mod into_robj;
 mod operators;
 mod rinternals;
@@ -231,51 +232,51 @@ pub trait Types: GetSexp {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     assert_eq!(r!(NULL).rtype(), RType::Null);
-    ///     assert_eq!(sym!(xyz).rtype(), RType::Symbol);
-    ///     assert_eq!(r!(Pairlist::from_pairs(vec![("a", r!(1))])).rtype(), RType::Pairlist);
-    ///     assert_eq!(R!("function() {}")?.rtype(), RType::Function);
-    ///     assert_eq!(Environment::new_with_parent(global_env()).rtype(), RType::Environment);
-    ///     assert_eq!(lang!("+", 1, 2).rtype(), RType::Language);
-    ///     assert_eq!(r!(Primitive::from_string("if")).rtype(), RType::Special);
-    ///     assert_eq!(r!(Primitive::from_string("+")).rtype(), RType::Builtin);
-    ///     assert_eq!(r!(Rstr::from_string("hello")).rtype(), RType::Rstr);
-    ///     assert_eq!(r!(TRUE).rtype(), RType::Logical);
-    ///     assert_eq!(r!(1).rtype(), RType::Integer);
-    ///     assert_eq!(r!(1.0).rtype(), RType::Real);
-    ///     assert_eq!(r!("1").rtype(), RType::String);
-    ///     assert_eq!(r!(List::from_values(&[1, 2])).rtype(), RType::List);
-    ///     assert_eq!(parse("x + y")?.rtype(), RType::Expression);
-    ///     assert_eq!(r!(Raw::from_bytes(&[1_u8, 2, 3])).rtype(), RType::Raw);
+    ///     assert_eq!(r!(NULL).rtype(), Rtype::Null);
+    ///     assert_eq!(sym!(xyz).rtype(), Rtype::Symbol);
+    ///     assert_eq!(r!(Pairlist::from_pairs(vec![("a", r!(1))])).rtype(), Rtype::Pairlist);
+    ///     assert_eq!(R!("function() {}")?.rtype(), Rtype::Function);
+    ///     assert_eq!(Environment::new_with_parent(global_env()).rtype(), Rtype::Environment);
+    ///     assert_eq!(lang!("+", 1, 2).rtype(), Rtype::Language);
+    ///     assert_eq!(r!(Primitive::from_string("if")).rtype(), Rtype::Special);
+    ///     assert_eq!(r!(Primitive::from_string("+")).rtype(), Rtype::Builtin);
+    ///     assert_eq!(r!(Rstr::from_string("hello")).rtype(), Rtype::Rstr);
+    ///     assert_eq!(r!(TRUE).rtype(), Rtype::Logicals);
+    ///     assert_eq!(r!(1).rtype(), Rtype::Integers);
+    ///     assert_eq!(r!(1.0).rtype(), Rtype::Doubles);
+    ///     assert_eq!(r!("1").rtype(), Rtype::Strings);
+    ///     assert_eq!(r!(List::from_values(&[1, 2])).rtype(), Rtype::List);
+    ///     assert_eq!(parse("x + y")?.rtype(), Rtype::Expressions);
+    ///     assert_eq!(r!(Raw::from_bytes(&[1_u8, 2, 3])).rtype(), Rtype::Raw);
     /// }
     /// ```
-    fn rtype(&self) -> RType {
+    fn rtype(&self) -> Rtype {
         match self.sexptype() {
-            NILSXP => RType::Null,
-            SYMSXP => RType::Symbol,
-            LISTSXP => RType::Pairlist,
-            CLOSXP => RType::Function,
-            ENVSXP => RType::Environment,
-            PROMSXP => RType::Promise,
-            LANGSXP => RType::Language,
-            SPECIALSXP => RType::Special,
-            BUILTINSXP => RType::Builtin,
-            CHARSXP => RType::Rstr,
-            LGLSXP => RType::Logical,
-            INTSXP => RType::Integer,
-            REALSXP => RType::Real,
-            CPLXSXP => RType::Complex,
-            STRSXP => RType::String,
-            DOTSXP => RType::Dot,
-            ANYSXP => RType::Any,
-            VECSXP => RType::List,
-            EXPRSXP => RType::Expression,
-            BCODESXP => RType::Bytecode,
-            EXTPTRSXP => RType::ExternalPtr,
-            WEAKREFSXP => RType::WeakRef,
-            RAWSXP => RType::Raw,
-            S4SXP => RType::S4,
-            _ => RType::Unknown,
+            NILSXP => Rtype::Null,
+            SYMSXP => Rtype::Symbol,
+            LISTSXP => Rtype::Pairlist,
+            CLOSXP => Rtype::Function,
+            ENVSXP => Rtype::Environment,
+            PROMSXP => Rtype::Promise,
+            LANGSXP => Rtype::Language,
+            SPECIALSXP => Rtype::Special,
+            BUILTINSXP => Rtype::Builtin,
+            CHARSXP => Rtype::Rstr,
+            LGLSXP => Rtype::Logicals,
+            INTSXP => Rtype::Integers,
+            REALSXP => Rtype::Doubles,
+            CPLXSXP => Rtype::Complexes,
+            STRSXP => Rtype::Strings,
+            DOTSXP => Rtype::Dot,
+            ANYSXP => Rtype::Any,
+            VECSXP => Rtype::List,
+            EXPRSXP => Rtype::Expressions,
+            BCODESXP => Rtype::Bytecode,
+            EXTPTRSXP => Rtype::ExternalPtr,
+            WEAKREFSXP => Rtype::WeakRef,
+            RAWSXP => Rtype::Raw,
+            S4SXP => Rtype::S4,
+            _ => Rtype::Unknown,
         }
     }
 
@@ -292,15 +293,15 @@ pub trait Types: GetSexp {
                 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())),
+                LGLSXP => Rany::Logicals(std::mem::transmute(self.as_robj())),
+                INTSXP => Rany::Integers(std::mem::transmute(self.as_robj())),
+                REALSXP => Rany::Doubles(std::mem::transmute(self.as_robj())),
+                CPLXSXP => Rany::Complexes(std::mem::transmute(self.as_robj())),
+                STRSXP => Rany::Strings(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())),
+                EXPRSXP => Rany::Expressions(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())),
@@ -812,6 +813,20 @@ pub trait Attributes: Types + Length {
         }
     }
 
+    /// Return true if an attribute exists.
+    fn has_attrib<'a, N>(&self, name: N) -> bool
+    where
+        Self: 'a,
+        Robj: From<N> + 'a,
+    {
+        let name = Robj::from(name);
+        if self.sexptype() == CHARSXP {
+            false
+        } else {
+            unsafe { Rf_getAttrib(self.get(), name.get()) != R_NilValue }
+        }
+    }
+
     /// Set a specific attribute and return the object.
     ///
     /// Note that some combinations of attributes are illegal and this will
@@ -857,6 +872,11 @@ pub trait Attributes: Types + Length {
         }
     }
 
+    /// Return true if this object has names.
+    fn has_names(&self) -> bool {
+        self.has_attrib(wrapper::symbol::names_symbol())
+    }
+
     /// Set the names attribute from a string iterator.
     ///
     /// Returns Error::NamesLengthMismatch if the length of the names does
@@ -1039,144 +1059,6 @@ impl PartialEq<Robj> for Robj {
     }
 }
 
-/// Implement {:?} formatting.
-impl std::fmt::Debug for Robj {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self.sexptype() {
-            NILSXP => write!(f, "r!(NULL)"),
-            SYMSXP => {
-                if self.is_missing_arg() {
-                    write!(f, "missing_arg()")
-                } else if self.is_unbound_value() {
-                    write!(f, "unbound_value()")
-                } else {
-                    write!(f, "sym!({})", self.as_symbol().unwrap().as_str())
-                }
-            }
-            LISTSXP => {
-                let pairlist = self.as_pairlist().unwrap().iter();
-                write!(f, "r!({:?})", pairlist)
-            }
-            CLOSXP => {
-                let func = self.as_function().unwrap();
-                let formals = func.formals();
-                let body = func.body();
-                let environment = func.environment();
-                write!(
-                    f,
-                    "r!(Function::from_parts({:?}, {:?}, {:?}))",
-                    formals, body, environment
-                )
-            }
-            ENVSXP => unsafe {
-                let sexp = self.get();
-                if sexp == R_GlobalEnv {
-                    write!(f, "global_env()")
-                } else if sexp == R_BaseEnv {
-                    write!(f, "base_env()")
-                } else if sexp == R_EmptyEnv {
-                    write!(f, "empty_env()")
-                } else {
-                    write!(f, "r!(Environment::from_pairs(...))")
-                }
-            },
-            PROMSXP => {
-                let p = self.as_promise().unwrap();
-                write!(
-                    f,
-                    "r!(Promise::from_parts({:?}, {:?}))",
-                    p.code(),
-                    p.environment()
-                )
-            }
-            LANGSXP => write!(
-                f,
-                "r!(Language::from_values({:?}))",
-                self.as_language().unwrap().values().collect::<Vec<_>>()
-            ),
-            SPECIALSXP => write!(f, "r!(Special())"),
-            BUILTINSXP => write!(f, "r!(Builtin())"),
-            CHARSXP => {
-                let c = Rstr::try_from(self.clone()).unwrap();
-                write!(f, "r!(Rstr::from_string({:?}))", c.as_str())
-            }
-            LGLSXP => {
-                let slice = self.as_logical_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "r!({:?})", slice[0])
-                } else {
-                    write!(f, "r!({:?})", slice)
-                }
-            }
-            INTSXP => {
-                let slice = self.as_integer_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "r!({:?})", slice[0])
-                } else {
-                    write!(f, "r!({:?})", self.as_integer_slice().unwrap())
-                }
-            }
-            REALSXP => {
-                let slice = self.as_real_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "r!({:?})", slice[0])
-                } else {
-                    write!(f, "r!({:?})", slice)
-                }
-            }
-            VECSXP => {
-                let list = self.as_list().unwrap();
-                if self.names().is_some() {
-                    write!(f, "r!(List::from_pairs({:?}))", list.iter())
-                } else {
-                    write!(f, "r!(List::from_values({:?}))", list.values())
-                }
-            }
-            EXPRSXP => write!(
-                f,
-                "r!(Expression::from_values({:?}))",
-                self.as_expression().unwrap().values()
-            ),
-            WEAKREFSXP => write!(f, "r!(Weakref())"),
-            // CPLXSXP => false,
-            STRSXP => {
-                write!(f, "r!([")?;
-                let mut sep = "";
-                for s in self.as_str_iter().unwrap() {
-                    // if s.is_na() {
-                    //     write!(f, "{}na_str()", sep)?;
-                    // } else {
-                    write!(f, "{}{:?}", sep, s)?;
-                    // }
-                    sep = ", ";
-                }
-                write!(f, "])")
-            }
-            DOTSXP => write!(f, "r!(Dot())"),
-            ANYSXP => write!(f, "r!(Any())"),
-            BCODESXP => write!(f, "r!(Bcode())"),
-            EXTPTRSXP => {
-                write!(f, "r!(ExternalPtr())")
-            }
-            RAWSXP => {
-                write!(
-                    f,
-                    "r!(Raw::from_bytes({:?}))",
-                    self.as_raw().unwrap().as_slice()
-                )
-            }
-            S4SXP => write!(f, "r!(S4())"),
-            NEWSXP => write!(f, "r!(New())"),
-            FREESXP => write!(f, "r!(Free())"),
-            _ => write!(f, "??"),
-        }?;
-        if let Some(c) = self.class() {
-            write!(f, ".set_class({:?}", c)?;
-        }
-        Ok(())
-    }
-}
-
 // Internal utf8 to str conversion.
 // Lets not worry about non-ascii/unicode strings for now (or ever).
 pub(crate) unsafe fn to_str<'a>(ptr: *const u8) -> &'a str {
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 2784805b3..0ca65b2a3 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -31,7 +31,7 @@ pub trait Rinternals: Types + Conversions {
     }
 
     /// Return true if this is an expression.
-    fn is_expression(&self) -> bool {
+    fn is_expressions(&self) -> bool {
         unsafe { Rf_isExpression(self.get()) != 0 }
     }
 
@@ -62,7 +62,7 @@ pub trait Rinternals: Types + Conversions {
 
     /// Return true if this is an expression.
     fn is_external_pointer(&self) -> bool {
-        self.rtype() == RType::ExternalPtr
+        self.rtype() == Rtype::ExternalPtr
     }
 
     /// Get the source ref.
@@ -391,12 +391,12 @@ pub trait Rinternals: Types + Conversions {
 
     /// Return true if this is RAWSXP.
     fn is_raw(&self) -> bool {
-        self.rtype() == RType::Raw
+        self.rtype() == Rtype::Raw
     }
 
     /// Return true if this is CHARSXP.
     fn is_char(&self) -> bool {
-        self.rtype() == RType::Rstr
+        self.rtype() == Rtype::Rstr
     }
 
     /// Check an external pointer tag.
@@ -467,9 +467,18 @@ pub trait Rinternals: Types + Conversions {
     }
 
     /// Generate a text representation of this object.
-    fn deparse(&self) -> Result<Robj> {
+    fn deparse(&self) -> Result<String> {
         use crate as extendr_api;
-        call!("deparse", self.as_robj())
+        let strings: Strings = call!("deparse", self.as_robj())?.try_into()?;
+        if strings.len() == 1 {
+            Ok(String::from(strings.elt(0).as_str()))
+        } else {
+            Ok(strings
+                .iter()
+                .map(|s| s.as_str())
+                .collect::<Vec<_>>()
+                .join(""))
+        }
     }
 }
 
diff --git a/extendr-api/src/scalar/macros.rs b/extendr-api/src/scalar/macros.rs
index 25b82da5f..812152de5 100644
--- a/extendr-api/src/scalar/macros.rs
+++ b/extendr-api/src/scalar/macros.rs
@@ -677,30 +677,6 @@ macro_rules! gen_trait_impl {
             }
         }
 
-        // The 'example usage' expands to...
-        //
-        // impl std::fmt::Debug for 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")
-        //         }
-        //     }
-        // }
-        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")
-                }
-            }
-        }
-
         // The 'example usage' expands to...
         //
         //
diff --git a/extendr-api/src/scalar/rbool.rs b/extendr-api/src/scalar/rbool.rs
index 807bd03a3..f36dd85d2 100644
--- a/extendr-api/src/scalar/rbool.rs
+++ b/extendr-api/src/scalar/rbool.rs
@@ -92,3 +92,20 @@ impl std::ops::Not for Rbool {
         }
     }
 }
+
+impl std::fmt::Debug for Rbool {
+    /// Debug format.
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            if self.is_na() {
+                "NA_LOGICAL"
+            } else if self.is_true() {
+                "TRUE"
+            } else {
+                "FALSE"
+            }
+        )
+    }
+}
diff --git a/extendr-api/src/scalar/rcplx_full.rs b/extendr-api/src/scalar/rcplx_full.rs
index b54ae5bc7..dc35d1291 100644
--- a/extendr-api/src/scalar/rcplx_full.rs
+++ b/extendr-api/src/scalar/rcplx_full.rs
@@ -23,6 +23,7 @@ impl CanBeNA for c64 {
 /// Rcplx has a special NA value, obtained from R headers via R_NaReal.
 ///
 /// Rcplx has the same footprint as R's complex value allowing us to use it in zero copy slices.
+#[derive(Debug)]
 pub struct Rcplx(c64);
 
 impl Rcplx {
diff --git a/extendr-api/src/scalar/rfloat.rs b/extendr-api/src/scalar/rfloat.rs
index 950403943..5e1b291ba 100644
--- a/extendr-api/src/scalar/rfloat.rs
+++ b/extendr-api/src/scalar/rfloat.rs
@@ -130,3 +130,14 @@ impl TryFrom<Robj> for Rfloat {
         Err(Error::ExpectedNumeric(robj))
     }
 }
+
+impl std::fmt::Debug for Rfloat {
+    /// Debug format.
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.is_na() {
+            write!(f, "NA_REAL")
+        } else {
+            self.inner().fmt(f)
+        }
+    }
+}
diff --git a/extendr-api/src/scalar/rint.rs b/extendr-api/src/scalar/rint.rs
index ab7fa0ecd..65ce3b66d 100644
--- a/extendr-api/src/scalar/rint.rs
+++ b/extendr-api/src/scalar/rint.rs
@@ -139,3 +139,14 @@ impl TryFrom<Robj> for Rint {
         Err(Error::ExpectedNumeric(robj))
     }
 }
+
+impl std::fmt::Debug for Rint {
+    /// Debug format.
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.is_na() {
+            write!(f, "NA_INTEGER")
+        } else {
+            self.inner().fmt(f)
+        }
+    }
+}
diff --git a/extendr-api/src/serializer.rs b/extendr-api/src/serializer.rs
index 203f1fda4..46896d628 100644
--- a/extendr-api/src/serializer.rs
+++ b/extendr-api/src/serializer.rs
@@ -1,13 +1,16 @@
 //! See https://serde.rs/impl-serializer.html
 
 use crate::error::{Error, Result};
-use crate::robj::{AsStrIter, GetSexp, Length, Rinternals, Types};
+use crate::na::CanBeNA;
+use crate::robj::{GetSexp, Length, Rinternals, Types};
+use crate::scalar::{Rbool, Rfloat, Rint};
 use crate::{
-    Doubles, Environment, Expression, Function, Integers, Language, Pairlist, Primitive, Promise,
-    Raw, Rstr, Symbol, S4,
+    Attributes, Doubles, Environment, Expressions, Function, Integers, Language, Logicals,
+    Pairlist, Primitive, Promise, Raw, Rstr, Strings, Symbol, S4,
 };
 use crate::{List, Rany, Robj};
 use serde::{ser, Serialize};
+// use crate::list;
 
 impl ser::Error for Error {
     fn custom<T: std::fmt::Display>(msg: T) -> Self {
@@ -523,7 +526,7 @@ impl ser::Serialize for Primitive {
     }
 }
 
-impl ser::Serialize for Expression {
+impl ser::Serialize for Expressions {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
@@ -563,8 +566,15 @@ impl ser::Serialize for Function {
     where
         S: ser::Serializer,
     {
-        let string = self.deparse().unwrap();
-        string.serialize(serializer)
+        serializer.serialize_unit()
+        // if let (Some(formals), Some(body), Some(environment)) = (
+        //     self.formals(), self.body(), self.environment()
+        // ) {
+        //     use crate as extendr_api;
+        //     list!(formals=formals, body=body, environment=environment).serialize(serializer)
+        // } else {
+        //     serializer.serialize_str("Function")
+        // }
     }
 }
 
@@ -619,7 +629,11 @@ impl ser::Serialize for List {
     where
         S: ser::Serializer,
     {
-        serializer.serialize_unit()
+        if self.has_names() {
+            serializer.collect_map(self.iter())
+        } else {
+            serializer.collect_seq(self.values())
+        }
     }
 }
 
@@ -629,15 +643,35 @@ impl ser::Serialize for Doubles {
         S: ser::Serializer,
     {
         if self.len() == 1 {
-            serializer.serialize_f64(self.elt(0).inner())
+            self.elt(0).serialize(serializer)
         } else {
-            use serde::ser::SerializeSeq;
-            let mut s = serializer.serialize_seq(Some(self.len()))?;
-            for v in self.as_robj().as_real_slice().unwrap() {
-                // TODO: use get_range when available.
-                s.serialize_element(v)?;
-            }
-            s.end()
+            serializer.collect_seq(self.iter())
+        }
+    }
+}
+
+impl ser::Serialize for Logicals {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        if self.len() == 1 {
+            self.elt(0).serialize(serializer)
+        } else {
+            serializer.collect_seq(self.iter())
+        }
+    }
+}
+
+impl ser::Serialize for Strings {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        if self.len() == 1 {
+            self.elt(0).serialize(serializer)
+        } else {
+            serializer.collect_seq(self.iter())
         }
     }
 }
@@ -647,7 +681,50 @@ impl ser::Serialize for Rstr {
     where
         S: ser::Serializer,
     {
-        serializer.serialize_str(self.as_str())
+        if self.is_na() {
+            serializer.serialize_unit()
+        } else {
+            serializer.serialize_str(self.as_str())
+        }
+    }
+}
+
+impl ser::Serialize for Rint {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        if self.is_na() {
+            serializer.serialize_unit()
+        } else {
+            serializer.serialize_i32(self.inner())
+        }
+    }
+}
+
+impl ser::Serialize for Rfloat {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        if self.is_na() {
+            serializer.serialize_unit()
+        } else {
+            serializer.serialize_f64(self.inner())
+        }
+    }
+}
+
+impl ser::Serialize for Rbool {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        if self.is_na() {
+            serializer.serialize_unit()
+        } else {
+            serializer.serialize_bool(self.is_true())
+        }
     }
 }
 
@@ -676,30 +753,15 @@ impl ser::Serialize for Robj {
             Rany::Special(value) => value.serialize(serializer),
             Rany::Builtin(value) => value.serialize(serializer),
             Rany::Rstr(value) => value.serialize(serializer),
-            Rany::Logical(_value) => {
-                todo!("implement Logicals")
-            }
-            Rany::Integer(value) => value.serialize(serializer),
-            Rany::Real(value) => value.serialize(serializer),
-            Rany::Complex(_complex) => {
-                todo!("implement Complexes")
-            }
-            Rany::String(string) => {
-                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 self.as_str_iter().unwrap() {
-                        s.serialize_element(v)?;
-                    }
-                    s.end()
-                }
-            }
+            Rany::Logicals(value) => value.serialize(serializer),
+            Rany::Integers(value) => value.serialize(serializer),
+            Rany::Doubles(value) => value.serialize(serializer),
+            Rany::Complexes(_complex) => serializer.serialize_unit(),
+            Rany::Strings(value) => value.serialize(serializer),
             Rany::Dot(_dot) => serializer.serialize_unit(),
             Rany::Any(_any) => serializer.serialize_unit(),
             Rany::List(value) => value.serialize(serializer),
-            Rany::Expression(value) => value.serialize(serializer),
+            Rany::Expressions(value) => value.serialize(serializer),
             Rany::Bytecode(_bytecode) => serializer.serialize_unit(),
             Rany::ExternalPtr(_externalptr) => serializer.serialize_unit(),
             Rany::WeakRef(_weakref) => serializer.serialize_unit(),
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index ae6796a9a..4df499721 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -40,7 +40,7 @@ macro_rules! make_from_iterator {
     };
 }
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Altrep {
     pub(crate) robj: Robj,
 }
@@ -94,7 +94,7 @@ pub trait AltrepImpl: Clone + std::fmt::Debug {
     }
 
     /// Coerce this object into some other type, if possible.
-    fn coerce(_x: SEXP, _ty: RType) -> Robj {
+    fn coerce(_x: SEXP, _ty: Rtype) -> Robj {
         ().into()
     }
 
@@ -535,7 +535,7 @@ impl Altrep {
         }
     }
 
-    fn altrep_class<StateType: AltrepImpl + 'static>(ty: RType, name: &str, base: &str) -> Robj {
+    fn altrep_class<StateType: AltrepImpl + 'static>(ty: Rtype, name: &str, base: &str) -> Robj {
         #![allow(non_snake_case)]
         #![allow(unused_variables)]
         use std::os::raw::c_int;
@@ -641,22 +641,22 @@ impl Altrep {
             let csbase = std::ffi::CString::new(base).unwrap();
 
             let class_ptr = match ty {
-                RType::Integer => {
+                Rtype::Integers => {
                     R_make_altinteger_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
-                RType::Real => {
+                Rtype::Doubles => {
                     R_make_altreal_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
-                RType::Logical => {
+                Rtype::Logicals => {
                     R_make_altlogical_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
-                RType::Raw => {
+                Rtype::Raw => {
                     R_make_altraw_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
-                RType::Complex => {
+                Rtype::Complexes => {
                     R_make_altcomplex_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
-                RType::String => {
+                Rtype::Strings => {
                     R_make_altstring_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
                 _ => panic!("expected Altvec compatible type"),
@@ -694,7 +694,7 @@ impl Altrep {
         use std::os::raw::c_int;
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::Integer, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Integers, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altinteger_Elt<StateType: AltIntegerImpl + 'static>(
@@ -772,7 +772,7 @@ impl Altrep {
         use std::os::raw::c_int;
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::Real, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Doubles, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altreal_Elt<StateType: AltRealImpl + 'static>(
@@ -847,7 +847,7 @@ impl Altrep {
         use std::os::raw::c_int;
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::Logical, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Logicals, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altlogical_Elt<StateType: AltLogicalImpl + 'static>(
@@ -908,7 +908,7 @@ impl Altrep {
         #![allow(non_snake_case)]
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::Raw, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Raw, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altraw_Elt<StateType: AltRawImpl + 'static>(
@@ -943,7 +943,7 @@ impl Altrep {
         #![allow(non_snake_case)]
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::Complex, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Complexes, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altcomplex_Elt<StateType: AltComplexImpl + 'static>(
@@ -979,7 +979,7 @@ impl Altrep {
         use std::os::raw::c_int;
 
         single_threaded(|| unsafe {
-            let class = Altrep::altrep_class::<StateType>(RType::String, name, base);
+            let class = Altrep::altrep_class::<StateType>(Rtype::Strings, name, base);
             let class_ptr = R_altrep_class_t { ptr: class.get() };
 
             unsafe extern "C" fn altstring_Elt<StateType: AltStringImpl + 'static>(
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
index c8117fce0..8e69b5b24 100644
--- a/extendr-api/src/wrapper/doubles.rs
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -15,7 +15,7 @@ use std::iter::FromIterator;
 ///     assert_eq!(sum, 60.0);
 /// }
 /// ```  
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Doubles {
     pub(crate) robj: Robj,
 }
@@ -80,3 +80,13 @@ impl DerefMut for Doubles {
         }
     }
 }
+
+impl std::fmt::Debug for Doubles {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.len() == 1 {
+            write!(f, "{:?}", self.elt(0))
+        } else {
+            f.debug_list().entries(self.iter()).finish()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/environment.rs b/extendr-api/src/wrapper/environment.rs
index 9ad98ebc1..805e289e3 100644
--- a/extendr-api/src/wrapper/environment.rs
+++ b/extendr-api/src/wrapper/environment.rs
@@ -1,6 +1,6 @@
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Environment {
     pub(crate) robj: Robj,
 }
@@ -242,3 +242,20 @@ impl Iterator for EnvIter {
         }
     }
 }
+
+impl std::fmt::Debug for Environment {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        unsafe {
+            let sexp = self.get();
+            if sexp == R_GlobalEnv {
+                write!(f, "global_env()")
+            } else if sexp == R_BaseEnv {
+                write!(f, "base_env()")
+            } else if sexp == R_EmptyEnv {
+                write!(f, "empty_env()")
+            } else {
+                write!(f, "{}", self.deparse().unwrap())
+            }
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/expr.rs b/extendr-api/src/wrapper/expr.rs
index 433ef9086..35c21fca6 100644
--- a/extendr-api/src/wrapper/expr.rs
+++ b/extendr-api/src/wrapper/expr.rs
@@ -1,19 +1,23 @@
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
-pub struct Expression {
+#[derive(PartialEq, Clone)]
+pub struct Expressions {
     pub(crate) robj: Robj,
 }
 
-impl Expression {
-    /// Wrapper for creating Expression (EXPRSXP) objects.
+impl Expressions {
+    /// Wrapper for creating Expressions (EXPRSXP) objects.
+    pub fn new() -> Self {
+        Expressions::from_values([Robj::from(()); 0])
+    }
+
+    /// Wrapper for creating Expressions (EXPRSXP) objects.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let expr = r!(Expression::from_values(&[r!(0), r!(1), r!(2)]));
-    ///     assert_eq!(expr.is_expression(), true);
+    ///     let expr = r!(Expressions::from_values(&[r!(0), r!(1), r!(2)]));
+    ///     assert_eq!(expr.is_expressions(), true);
     ///     assert_eq!(expr.len(), 3);
-    ///     assert_eq!(format!("{:?}", expr), r#"r!(Expression::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
     pub fn from_values<V>(values: V) -> Self
@@ -32,3 +36,17 @@ impl Expression {
         ListIter::from_parts(self.robj.clone(), 0, self.robj.len())
     }
 }
+
+impl std::default::Default for Expressions {
+    fn default() -> Self {
+        Expressions::new()
+    }
+}
+
+impl std::fmt::Debug for Expressions {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Expressions")
+            .field("values", &self.values())
+            .finish()
+    }
+}
diff --git a/extendr-api/src/wrapper/externalptr.rs b/extendr-api/src/wrapper/externalptr.rs
index 6d81f7077..f7050e94c 100644
--- a/extendr-api/src/wrapper/externalptr.rs
+++ b/extendr-api/src/wrapper/externalptr.rs
@@ -1,5 +1,6 @@
 use super::*;
 use std::any::Any;
+use std::fmt::Debug;
 
 /// Wrapper for creating R objects containing any Rust object.
 ///
@@ -14,8 +15,8 @@ use std::any::Any;
 /// }
 /// ```
 ///
-#[derive(Debug, PartialEq, Clone)]
-pub struct ExternalPtr<T> {
+#[derive(PartialEq, Clone)]
+pub struct ExternalPtr<T: Debug + 'static> {
     /// This is the contained Robj.
     pub(crate) robj: Robj,
 
@@ -23,16 +24,23 @@ pub struct ExternalPtr<T> {
     marker: std::marker::PhantomData<T>,
 }
 
-impl<T> Deref for ExternalPtr<T> {
+impl<T: Debug + 'static> 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>() }
+        self.addr()
     }
 }
 
-impl<T: Any> ExternalPtr<T> {
+impl<T: Debug + 'static> DerefMut for ExternalPtr<T> {
+    /// This allows us to treat the Robj as if it is the mutable type T.
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.addr_mut()
+    }
+}
+
+impl<T: Any + Debug> 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.
@@ -75,21 +83,39 @@ impl<T: Any> ExternalPtr<T> {
     // 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 {
+    pub fn 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 {
+    pub fn protected(&self) -> Robj {
         unsafe { new_owned(R_ExternalPtrProtected(self.robj.get())) }
     }
+
+    /// Get the "address" field of an external pointer.
+    /// Normally, we will use Deref to do this.
+    pub fn addr<'a>(&self) -> &'a T {
+        unsafe {
+            let ptr = R_ExternalPtrAddr(self.robj.get()) as *const T;
+            &*ptr as &'a T
+        }
+    }
+
+    /// Get the "address" field of an external pointer as a mutable reference.
+    /// Normally, we will use DerefMut to do this.
+    pub fn addr_mut(&mut self) -> &mut T {
+        unsafe {
+            let ptr = R_ExternalPtrAddr(self.robj.get()) as *mut T;
+            &mut *ptr as &mut T
+        }
+    }
 }
 
-impl<T: Any> TryFrom<Robj> for ExternalPtr<T> {
+impl<T: Any + Debug> TryFrom<Robj> for ExternalPtr<T> {
     type Error = Error;
 
     fn try_from(robj: Robj) -> Result<Self> {
-        if robj.rtype() != RType::ExternalPtr {
+        if robj.rtype() != Rtype::ExternalPtr {
             return Err(Error::ExpectedExternalPtr(robj));
         }
 
@@ -100,7 +126,7 @@ impl<T: Any> TryFrom<Robj> for ExternalPtr<T> {
 
         // Check the type name.
         let type_name = std::any::type_name::<T>();
-        if res.external_ptr_tag().as_str() != Some(type_name) {
+        if res.tag().as_str() != Some(type_name) {
             return Err(Error::ExpectedExternalPtrType(res.robj, type_name.into()));
         }
 
@@ -108,8 +134,14 @@ impl<T: Any> TryFrom<Robj> for ExternalPtr<T> {
     }
 }
 
-impl<T: Any> From<ExternalPtr<T>> for Robj {
+impl<T: Any + Debug> From<ExternalPtr<T>> for Robj {
     fn from(val: ExternalPtr<T>) -> Self {
         val.robj
     }
 }
+
+impl<T: Debug + 'static> std::fmt::Debug for ExternalPtr<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        (&*self as &T).fmt(f)
+    }
+}
diff --git a/extendr-api/src/wrapper/function.rs b/extendr-api/src/wrapper/function.rs
index a08f39ed9..daf46de79 100644
--- a/extendr-api/src/wrapper/function.rs
+++ b/extendr-api/src/wrapper/function.rs
@@ -23,7 +23,7 @@ use super::*;
 ///     assert_eq!(func.environment(), None);
 /// }
 /// ```
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Function {
     pub(crate) robj: Robj,
 }
@@ -69,7 +69,7 @@ impl Function {
     /// Get the formal arguments of the function or None if it is a primitive.
     pub fn formals(&self) -> Option<Pairlist> {
         unsafe {
-            if self.rtype() == RType::Function {
+            if self.rtype() == Rtype::Function {
                 let sexp = self.robj.get();
                 Some(Robj::from_sexp(FORMALS(sexp)).try_into().unwrap())
             } else {
@@ -81,7 +81,7 @@ impl Function {
     /// Get the body of the function or None if it is a primitive.
     pub fn body(&self) -> Option<Robj> {
         unsafe {
-            if self.rtype() == RType::Function {
+            if self.rtype() == Rtype::Function {
                 let sexp = self.robj.get();
                 Some(Robj::from_sexp(BODY(sexp)))
             } else {
@@ -93,7 +93,7 @@ impl Function {
     /// Get the environment of the function or None if it is a primitive.
     pub fn environment(&self) -> Option<Environment> {
         unsafe {
-            if self.rtype() == RType::Function {
+            if self.rtype() == Rtype::Function {
                 let sexp = self.robj.get();
                 Some(
                     Robj::from_sexp(CLOENV(sexp))
@@ -106,3 +106,9 @@ impl Function {
         }
     }
 }
+
+impl std::fmt::Debug for Function {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.deparse().unwrap())
+    }
+}
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index 7b8c3365c..a2667c57e 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -15,7 +15,7 @@ use std::iter::FromIterator;
 ///     assert_eq!(sum, 60);
 /// }
 /// ```  
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Integers {
     pub(crate) robj: Robj,
 }
@@ -146,3 +146,13 @@ impl DerefMut for Integers {
         }
     }
 }
+
+impl std::fmt::Debug for Integers {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.len() == 1 {
+            write!(f, "{:?}", self.elt(0))
+        } else {
+            f.debug_list().entries(self.iter()).finish()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/lang.rs b/extendr-api/src/wrapper/lang.rs
index d0b083668..f791b9b6f 100644
--- a/extendr-api/src/wrapper/lang.rs
+++ b/extendr-api/src/wrapper/lang.rs
@@ -11,7 +11,7 @@ use super::*;
 /// ```
 ///
 /// Note: You can use the [lang!] macro for this.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Language {
     pub(crate) robj: Robj,
 }
@@ -54,3 +54,21 @@ impl Language {
         self.iter().map(|(_, robj)| robj)
     }
 }
+
+impl std::fmt::Debug for Language {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "lang!({})",
+            self.iter()
+                .map(|(k, v)| if k.is_empty() {
+                    format!("{:?}", v)
+                } else {
+                    format!("{}={:?}", k, v)
+                })
+                .collect::<Vec<_>>()
+                .join(", ")
+        )?;
+        Ok(())
+    }
+}
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
index 89057d1fa..4e848da9c 100644
--- a/extendr-api/src/wrapper/list.rs
+++ b/extendr-api/src/wrapper/list.rs
@@ -3,7 +3,7 @@ use std::iter::FromIterator;
 
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct List {
     pub(crate) robj: Robj,
 }
@@ -139,7 +139,7 @@ impl List {
         // TODO: Make a proper NamedListIter.
         self.names()
             .map(|n| n.zip(self.values()))
-            .unwrap_or_else(|| StrIter::new().zip(self.values()))
+            .unwrap_or_else(|| StrIter::new(self.len()).zip(self.values()))
     }
 
     /// Get the list a slice of `Robj`s.
@@ -404,3 +404,31 @@ impl Deref for List {
         self.as_slice()
     }
 }
+
+impl std::fmt::Debug for List {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.names().is_none() {
+            write!(
+                f,
+                "list!({})",
+                self.values()
+                    .map(|v| format!("{:?}", v))
+                    .collect::<Vec<_>>()
+                    .join(", ")
+            )
+        } else {
+            write!(
+                f,
+                "list!({})",
+                self.iter()
+                    .map(|(k, v)| if !k.is_empty() {
+                        format!("{}={:?}", k, v)
+                    } else {
+                        format!("{:?}", v)
+                    })
+                    .collect::<Vec<_>>()
+                    .join(", ")
+            )
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/logicals.rs b/extendr-api/src/wrapper/logicals.rs
index 278b004ad..a63fd62d2 100644
--- a/extendr-api/src/wrapper/logicals.rs
+++ b/extendr-api/src/wrapper/logicals.rs
@@ -16,7 +16,7 @@ use std::iter::FromIterator;
 ///     assert_eq!(vec[1], false);
 /// }
 /// ```  
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Logicals {
     pub(crate) robj: Robj,
 }
@@ -137,3 +137,13 @@ impl DerefMut for Logicals {
         }
     }
 }
+
+impl std::fmt::Debug for Logicals {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.len() == 1 {
+            write!(f, "{:?}", self.elt(0))
+        } else {
+            f.debug_list().entries(self.iter()).finish()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 9a1481134..89ce2a787 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -36,7 +36,7 @@ pub use altrep::{
 pub use complexes::Complexes;
 pub use doubles::Doubles;
 pub use environment::{EnvIter, Environment};
-pub use expr::Expression;
+pub use expr::Expressions;
 pub use externalptr::ExternalPtr;
 pub use function::Function;
 pub use integers::Integers;
@@ -168,9 +168,9 @@ make_conversions!(
 make_conversions!(List, ExpectedList, is_list, "Not a List");
 
 make_conversions!(
-    Expression,
+    Expressions,
     ExpectedExpression,
-    is_expression,
+    is_expressions,
     "Not an Expression"
 );
 
@@ -262,7 +262,6 @@ pub trait Conversions: GetSexp {
     ///     let call_to_xyz = r!(Language::from_values(&[r!(Symbol::from_string("xyz")), r!(1), r!(2)]));
     ///     assert_eq!(call_to_xyz.is_language(), true);
     ///     assert_eq!(call_to_xyz.len(), 3);
-    ///     assert_eq!(format!("{:?}", call_to_xyz), r#"r!(Language::from_values([sym!(xyz), r!(1), r!(2)]))"#);
     /// }
     /// ```
     fn as_language(&self) -> Option<Language> {
@@ -289,7 +288,6 @@ pub trait Conversions: GetSexp {
     /// test! {
     ///     let list = r!(List::from_values(&[r!(0), r!(1), r!(2)]));
     ///     assert_eq!(list.is_list(), true);
-    ///     assert_eq!(format!("{:?}", list), r#"r!(List::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
     fn as_list(&self) -> Option<List> {
@@ -300,14 +298,13 @@ pub trait Conversions: GetSexp {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let expr = r!(Expression::from_values(&[r!(0), r!(1), r!(2)]));
-    ///     assert_eq!(expr.is_expression(), true);
-    ///     assert_eq!(expr.as_expression(), Some(Expression::from_values(vec![r!(0), r!(1), r!(2)])));
-    ///     assert_eq!(format!("{:?}", expr), r#"r!(Expression::from_values([r!(0), r!(1), r!(2)]))"#);
+    ///     let expr = r!(Expressions::from_values(&[r!(0), r!(1), r!(2)]));
+    ///     assert_eq!(expr.is_expressions(), true);
+    ///     assert_eq!(expr.as_expressions(), Some(Expressions::from_values(vec![r!(0), r!(1), r!(2)])));
     /// }
     /// ```
-    fn as_expression(&self) -> Option<Expression> {
-        Expression::try_from(self.as_robj().clone()).ok()
+    fn as_expressions(&self) -> Option<Expressions> {
+        Expressions::try_from(self.as_robj().clone()).ok()
     }
 
     /// Convert an environment object (ENVSXP) to a Env wrapper.
diff --git a/extendr-api/src/wrapper/pairlist.rs b/extendr-api/src/wrapper/pairlist.rs
index 009487052..969b08988 100644
--- a/extendr-api/src/wrapper/pairlist.rs
+++ b/extendr-api/src/wrapper/pairlist.rs
@@ -2,7 +2,7 @@
 
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Pairlist {
     pub(crate) robj: Robj,
 }
@@ -200,3 +200,17 @@ impl From<()> for Pairlist {
         }
     }
 }
+
+impl std::fmt::Debug for Pairlist {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "pairlist!({})",
+            self.iter()
+                .map(|(k, v)| format!("{}={:?}", k, v))
+                .collect::<Vec<_>>()
+                .join(", ")
+        )?;
+        Ok(())
+    }
+}
diff --git a/extendr-api/src/wrapper/primitive.rs b/extendr-api/src/wrapper/primitive.rs
index 30fad3651..14a3bcc28 100644
--- a/extendr-api/src/wrapper/primitive.rs
+++ b/extendr-api/src/wrapper/primitive.rs
@@ -11,7 +11,7 @@ use super::*;
 /// }
 /// ```
 ///
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Primitive {
     pub(crate) robj: Robj,
 }
@@ -23,8 +23,8 @@ impl Primitive {
     /// test! {
     ///     let builtin = r!(Primitive::from_string("+")?);
     ///     let special = r!(Primitive::from_string("if")?);
-    ///     assert_eq!(builtin.rtype(), RType::Builtin);
-    ///     assert_eq!(special.rtype(), RType::Special);
+    ///     assert_eq!(builtin.rtype(), Rtype::Builtin);
+    ///     assert_eq!(special.rtype(), Rtype::Special);
     /// }
     /// ```
     pub fn from_string(val: &str) -> Result<Self> {
@@ -42,3 +42,10 @@ impl Primitive {
 
     // There is currently no way to convert a primitive to a string.
 }
+
+impl std::fmt::Debug for Primitive {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let s: String = self.robj.deparse().unwrap();
+        write!(f, "{:?}", s)
+    }
+}
diff --git a/extendr-api/src/wrapper/promise.rs b/extendr-api/src/wrapper/promise.rs
index 1da6eeaf7..ff05d9c27 100644
--- a/extendr-api/src/wrapper/promise.rs
+++ b/extendr-api/src/wrapper/promise.rs
@@ -1,7 +1,7 @@
 use super::*;
 
 /// Wrapper for creating promises (PROMSXP).
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Promise {
     pub(crate) robj: Robj,
 }
@@ -78,3 +78,12 @@ impl Promise {
         }
     }
 }
+
+impl std::fmt::Debug for Promise {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Promise")
+            .field("code", &self.code())
+            .field("environment", &self.environment())
+            .finish()
+    }
+}
diff --git a/extendr-api/src/wrapper/raw.rs b/extendr-api/src/wrapper/raw.rs
index 9adb67e18..3b9bbc6ff 100644
--- a/extendr-api/src/wrapper/raw.rs
+++ b/extendr-api/src/wrapper/raw.rs
@@ -11,7 +11,7 @@ use super::*;
 /// }
 /// ```
 ///
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Raw {
     pub(crate) robj: Robj,
 }
@@ -50,3 +50,10 @@ impl Raw {
         self.robj.as_typed_slice().unwrap()
     }
 }
+
+impl std::fmt::Debug for Raw {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("Raw")?;
+        f.debug_list().entries(self.as_slice()).finish()
+    }
+}
diff --git a/extendr-api/src/wrapper/rstr.rs b/extendr-api/src/wrapper/rstr.rs
index 43dbdb671..1525b7aa6 100644
--- a/extendr-api/src/wrapper/rstr.rs
+++ b/extendr-api/src/wrapper/rstr.rs
@@ -12,7 +12,7 @@ use super::*;
 /// }
 /// ```
 ///
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct Rstr {
     pub(crate) robj: Robj,
 }
@@ -86,3 +86,31 @@ impl PartialEq<str> for Rstr {
         self.as_str() == other
     }
 }
+
+impl std::fmt::Debug for Rstr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let s = self.as_str();
+        write!(f, "{:?}", s)
+    }
+}
+
+impl std::fmt::Display for Rstr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let s = self.as_str();
+        write!(f, "{}", s)
+    }
+}
+
+impl CanBeNA for Rstr {
+    fn is_na(&self) -> bool {
+        unsafe { self.robj.get() == R_NaString }
+    }
+
+    fn na() -> Self {
+        unsafe {
+            Self {
+                robj: Robj::from_sexp(R_NaString),
+            }
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/s4.rs b/extendr-api/src/wrapper/s4.rs
index 0b37122fc..c20a28ea1 100644
--- a/extendr-api/src/wrapper/s4.rs
+++ b/extendr-api/src/wrapper/s4.rs
@@ -2,7 +2,7 @@
 
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct S4 {
     pub(crate) robj: Robj,
 }
@@ -156,3 +156,9 @@ impl S4 {
 //         valid: *mut *const ::std::os::raw::c_char,
 //     ) -> ::std::os::raw::c_int;
 // }
+
+impl std::fmt::Debug for S4 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("S4").finish()
+    }
+}
diff --git a/extendr-api/src/wrapper/strings.rs b/extendr-api/src/wrapper/strings.rs
index 5842e4c74..b521c34b9 100644
--- a/extendr-api/src/wrapper/strings.rs
+++ b/extendr-api/src/wrapper/strings.rs
@@ -3,7 +3,7 @@ use std::iter::FromIterator;
 
 use super::*;
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Strings {
     pub(crate) robj: Robj,
 }
@@ -69,12 +69,16 @@ impl Strings {
     }
 
     /// This is a relatively expensive operation, so use a variable if using this in a loop.
-    pub fn elt(&self, i: usize) -> &str {
-        if i >= self.len() {
-            <&str>::na()
-        } else {
-            use crate::wrapper::rstr::sexp_to_str;
-            unsafe { sexp_to_str(STRING_ELT(self.get(), i as R_xlen_t)) }
+    pub fn elt(&self, i: usize) -> Rstr {
+        unsafe {
+            let sexp = if i >= self.len() {
+                R_NaString
+            } else {
+                STRING_ELT(self.get(), i as R_xlen_t)
+            };
+            Rstr {
+                robj: Robj::from_sexp(sexp),
+            }
         }
     }
 
@@ -141,3 +145,13 @@ impl Deref for Strings {
         self.as_slice()
     }
 }
+
+impl std::fmt::Debug for Strings {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.len() == 1 {
+            write!(f, "{:?}", self.elt(0))
+        } else {
+            f.debug_list().entries(self.iter()).finish()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/symbol.rs b/extendr-api/src/wrapper/symbol.rs
index 81cbb5a1f..334f4f974 100644
--- a/extendr-api/src/wrapper/symbol.rs
+++ b/extendr-api/src/wrapper/symbol.rs
@@ -10,7 +10,7 @@ use super::*;
 /// }
 /// ```
 ///
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct Symbol {
     pub(crate) robj: Robj,
 }


Test patch:
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index 4c7aaacfe..28c116399 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -1,41 +1,5 @@
 use crate::*;
 
-#[test]
-fn test_debug() {
-    test! {
-        // Special values
-        assert_eq!(format!("{:?}", r!(NULL)), "r!(NULL)");
-        assert_eq!(format!("{:?}", r!(TRUE)), "r!(true)");
-        assert_eq!(format!("{:?}", r!(FALSE)), "r!(false)");
-
-        // Scalars
-        assert_eq!(format!("{:?}", r!(1)), "r!(1)");
-        assert_eq!(format!("{:?}", r!(1.)), "r!(1.0)");
-        assert_eq!(format!("{:?}", r!("hello")), "r!([\"hello\"])");
-        let s = "hello".to_string();
-        assert_eq!(format!("{:?}", r!(s)), "r!([\"hello\"])");
-
-        // Vectors
-        assert_eq!(format!("{:?}", r!([1, 2, 3])), "r!([1, 2, 3])");
-        assert_eq!(format!("{:?}", r!([1., 2., 3.])), "r!([1.0, 2.0, 3.0])");
-        assert_eq!(format!("{:?}", r!(Raw::from_bytes(&[1, 2, 3]))), "r!(Raw::from_bytes([1, 2, 3]))");
-
-        // Wrappers
-        assert_eq!(format!("{:?}", r!(Symbol::from_string("x"))), "sym!(x)");
-        assert_eq!(format!("{:?}", r!(Rstr::from_string("x"))), "r!(Rstr::from_string(\"x\"))");
-        assert_eq!(
-            format!("{:?}", r!(Language::from_values(&[r!(Symbol::from_string("x"))]))),
-            "r!(Language::from_values([sym!(x)]))"
-        );
-
-        // Logical
-        assert_eq!(
-            format!("{:?}", r!([TRUE, FALSE, NA_LOGICAL])),
-            "r!([true, false, na])"
-        );
-    }
-}
-
 #[test]
 fn test_from_robj() {
     test! {
@@ -280,8 +244,6 @@ fn test_to_robj() {
         let ab = Robj::from(vec!["a", "b"]);
         let ab2 = Robj::from(vec!["a".to_string(), "b".to_string()]);
         assert_eq!(ab, ab2);
-        assert_eq!(format!("{:?}", ab), "r!([\"a\", \"b\"])");
-        assert_eq!(format!("{:?}", ab2), "r!([\"a\", \"b\"])");
 
         assert_eq!(Robj::from(Some(1)), Robj::from(1));
         assert!(!Robj::from(Some(1)).is_na());
@@ -305,10 +267,10 @@ fn test_to_robj() {
 fn parse_test() {
     test! {
     let p = parse("print(1L);print(1L);")?;
-    let q = r!(Expression::from_values(&[
+    let q = Expressions::from_values(&[
         r!(Language::from_values(&[r!(Symbol::from_string("print")), r!(1)])),
         r!(Language::from_values(&[r!(Symbol::from_string("print")), r!(1)]))
-    ]));
+    ]);
     assert_eq!(p, q);
 
     let p = eval_string("1L + 1L")?;
diff --git a/extendr-api/tests/debug_tests.rs b/extendr-api/tests/debug_tests.rs
new file mode 100644
index 000000000..51e652d4e
--- /dev/null
+++ b/extendr-api/tests/debug_tests.rs
@@ -0,0 +1,64 @@
+use extendr_api::prelude::*;
+
+#[test]
+fn test_debug() {
+    test! {
+        let r = r!(());
+        assert_eq!(format!("{:?}", r), "()");
+        let r : Symbol = sym!("xyz").try_into().unwrap();
+        assert_eq!(format!("{:?}", r), "sym!(\"xyz\")");
+        let r : Pairlist = pairlist!(x=1);
+        assert_eq!(format!("{:?}", r), "pairlist!(x=1)");
+        let r : Function = R!("function() 1").unwrap().try_into().unwrap();
+        assert_eq!(format!("{:?}", r), "function () 1");
+        let r = global_env();
+        assert_eq!(format!("{:?}", r), "global_env()");
+        let r = empty_env();
+        assert_eq!(format!("{:?}", r), "empty_env()");
+        let r = base_env();
+        assert_eq!(format!("{:?}", r), "base_env()");
+        let r = Environment::new_with_parent(global_env());
+        assert_eq!(format!("{:?}", r), "<environment>");
+        let r = Promise::from_parts(r!(1), global_env())?;
+        assert_eq!(format!("{:?}", r), "Promise { code: 1, environment: global_env() }");
+        let r : Language = lang!("x").try_into()?;
+        assert_eq!(format!("{:?}", r), "lang!(sym!(x))");
+        let r : Language = lang!("x", 1, 2).try_into()?;
+        assert_eq!(format!("{:?}", r), "lang!(sym!(x), 1, 2)");
+        let r : Primitive = R!("`+`")?.try_into()?;
+        assert_eq!(format!("{:?}", r), "\".Primitive(\\\"+\\\")\"");
+        let r : Primitive  = R!("`if`")?.try_into()?;
+        assert_eq!(format!("{:?}", r), "\".Primitive(\\\"if\\\")\"");
+        let r : Rstr = Rstr::from_string("xyz");
+        assert_eq!(format!("{:?}", r), "\"xyz\"");
+        let r : Logicals = Logicals::from_values([TRUE]);
+        assert_eq!(format!("{:?}", r), "TRUE");
+        let r : Logicals = Logicals::from_values([TRUE, FALSE, NA_LOGICAL]);
+        assert_eq!(format!("{:?}", r), "[TRUE, FALSE, NA_LOGICAL]");
+        let r : Integers = Integers::new(1);
+        assert_eq!(format!("{:?}", r), "0");
+        let r : Integers = Integers::new(2);
+        assert_eq!(format!("{:?}", r), "[0, 0]");
+        let r : Doubles = Doubles::new(1);
+        assert_eq!(format!("{:?}", r), "0.0");
+        let r : Doubles = Doubles::new(2);
+        assert_eq!(format!("{:?}", r), "[0.0, 0.0]");
+        let r : Strings = Strings::from_values(["xyz"]);
+        assert_eq!(format!("{:?}", r), "\"xyz\"");
+        let r : Strings = Strings::from_values(["xyz", "abc"]);
+        assert_eq!(format!("{:?}", r), "[\"xyz\", \"abc\"]");
+        let r : List = list!(a=1, b=2);
+        assert_eq!(format!("{:?}", r), "list!(a=1, b=2)");
+        let r : List = list!(1, 2);
+        assert_eq!(format!("{:?}", r), "list!(1, 2)");
+        let r : List = list!(1, b=2);
+        assert_eq!(format!("{:?}", r), "list!(1, b=2)");
+        let r : Expressions = parse("1 + 2").unwrap();
+        assert_eq!(format!("{:?}", r), "Expressions { values: [lang!(sym!(+), 1.0, 2.0)] }");
+        let r : Raw = Raw::new(2);
+        assert_eq!(format!("{:02x?}", r), "Raw[00, 00]");
+        S4::set_class("fred", pairlist!(x="numeric"), r!(()))?;
+        let r : S4 = S4::new("fred")?;
+        assert_eq!(format!("{:?}", r), "S4");
+    }
+}
diff --git a/extendr-api/tests/externalptr_test.rs b/extendr-api/tests/externalptr_test.rs
index c22615b68..16756b5d2 100644
--- a/extendr-api/tests/externalptr_test.rs
+++ b/extendr-api/tests/externalptr_test.rs
@@ -21,6 +21,7 @@ fn test_externalptr_drop() {
         }
 
         // Dummy structure that will show if we drop correctly.
+        #[derive(Debug)]
         struct X {
         }
 
@@ -50,6 +51,7 @@ fn test_externalptr_drop() {
 #[test]
 fn test_externalptr_deref() {
     test! {
+        #[derive(Debug)]
         struct X {
             x: i32,
             y: i32,
diff --git a/extendr-api/tests/serializer_tests.rs b/extendr-api/tests/serializer_tests.rs
index 0b03c8015..07b20ca99 100644
--- a/extendr-api/tests/serializer_tests.rs
+++ b/extendr-api/tests/serializer_tests.rs
@@ -57,63 +57,50 @@ mod test {
     #[test]
     fn test_serialize_robj() {
         test! {
-            #[derive(Serialize)]
-            struct Test {
-                null: Robj,
-                symbol: Symbol,
-                pairlist: Pairlist,
-                function: Function,
-                // environment: Environment,
-                // promise: Promise,
-                // language: Language,
-                // special: Primitive,
-                // builtin: Primitive,
-                rstr: Rstr,
-                // logical: Robj,
-                integer: Integers,
-                real: Doubles,
-                // complex: Robj,
-                // string: Robj,
-                // dot: Dot,
-                // any: Any,
-                list: List,
-                // expression: Expression,
-                // bytecode: Bytecode,
-                // externalptr: ExternalPtr,
-                // weakref: WeakRef,
-                raw: Raw,
-                // s4: S4,
-            }
-
-            let _test = Test {
-                null: r!(()),
-                symbol: sym!("xyz").try_into()?,
-                pairlist: pairlist!(x=1),
-                function: R!("function() 1")?.try_into()?,
-                // environment: Environment::new_with_parent(global_env()),
-                // promise: Promise::from_parts(r!(1), global_env())?,
-                // language: lang!("x").try_into()?,
-                // special: r!(()),
-                // builtin: r!(()),
-                rstr: Rstr::from_string("xyz"),
-                // logical: r!(TRUE),
-                integer: Integers::new(10),
-                real: Doubles::new(10),
-                // complex: R!("complex(10)")?,
-                // string: r!("xyz"),
-                // dot: Dot,
-                // any: Any,
-                list: list!(a=1, b=2).try_into()?,
-                // expression: Expression,
-                // bytecode: Bytecode,
-                // externalptr: ExternalPtr,
-                // weakref: WeakRef,
-                raw: Raw::new(10),
-                // s4: S4,
-            };
-
-            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));
+            let s = r!(());
+            assert_eq!(to_robj(&s).unwrap(), r!(()));
+            let s = r!(sym!(xyz));
+            assert_eq!(to_robj(&s).unwrap(), r!("xyz"));
+            let s = r!(pairlist!(x=1, y=2));
+            assert_eq!(to_robj(&s).unwrap(), r!(list!(x=1, y=2)));
+            let s = R!("function (x) 1").unwrap();
+            assert_eq!(to_robj(&s).unwrap(), r!(()));
+            let s = r!(Environment::new_with_parent(global_env()));
+            assert_eq!(to_robj(&s).unwrap(), r!(()));
+            let s = r!(Promise::from_parts(r!(()), global_env()));
+            assert_eq!(to_robj(&s).unwrap(), r!(()));
+            let s = r!(Language::from_values([r!(())]));
+            assert_eq!(to_robj(&s).unwrap(), r!(()));
+            let s = r!(Primitive::from_string("+"));
+            assert_eq!(to_robj(&s).unwrap(), r!(".Primitive(\"+\")"));
+            let s = r!(Primitive::from_string("if"));
+            assert_eq!(to_robj(&s).unwrap(), r!(".Primitive(\"if\")"));
+            let s = r!(Rstr::from_string("xyz"));
+            assert_eq!(to_robj(&s).unwrap(), r!("xyz"));
+            let s = r!([TRUE, FALSE, NA_LOGICAL]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list!(TRUE, FALSE, ())));
+            let s = r!([1, 2, 3]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list![1, 2, 3]));
+            let s = r!([1., 2., 3.]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list![1., 2., 3.]));
+            // Rany::Complexes(_complex) => serializer.serialize_unit(),
+            let s = r!(["1", "2", "3"]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list!["1", "2", "3"]));
+            // Rany::Dot(_dot) => serializer.serialize_unit(),
+            // Rany::Any(_any) => serializer.serialize_unit(),
+            let s = r!(list![1, 2., "3"]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list![1, 2., "3"]));
+            let s = r!(list![a=1, b=2., c="3"]);
+            assert_eq!(to_robj(&s).unwrap(), r!(list![a=1, b=2., c="3"]));
+            let s = r!(parse("1 + 2"));
+            assert_eq!(to_robj(&s).unwrap(), r!("expression(1 + 2)"));
+            // Rany::Bytecode(_bytecode) => serializer.serialize_unit(),
+            // Rany::ExternalPtr(_externalptr) => serializer.serialize_unit(),
+            // Rany::WeakRef(_weakref) => serializer.serialize_unit(),
+            let s = r!(Raw::from_bytes(&[1, 2, 3]));
+            assert_eq!(to_robj(&s).unwrap(), r!(Raw::from_bytes(&[1, 2, 3])));
+            // Rany::S4(value) => value.serialize(serializer),
+            // Rany::Unknown(_unknown) => serializer.serialize_unit(),
         }
     }
 }
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index 793aaec29..1f3e5e38d 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -5,11 +5,11 @@ fn test_strings() {
     test! {
         let s = Strings::new(10);
         assert_eq!(s.len(), 10);
-        assert_eq!(s.rtype(), RType::String);
+        assert_eq!(s.rtype(), Rtype::Strings);
 
         let mut s = Strings::from_values(["x", "y", "z"]);
         assert_eq!(s.len(), 3);
-        assert_eq!(s.rtype(), RType::String);
+        assert_eq!(s.rtype(), Rtype::Strings);
         assert_eq!(s.elt(0), "x");
         assert_eq!(s.elt(1), "y");
         assert_eq!(s.elt(2), "z");
@@ -44,11 +44,11 @@ fn test_list() {
     test! {
         let s = List::new(10);
         assert_eq!(s.len(), 10);
-        assert_eq!(s.rtype(), RType::List);
+        assert_eq!(s.rtype(), Rtype::List);
 
         let mut s = List::from_values(["x", "y", "z"]);
         assert_eq!(s.len(), 3);
-        assert_eq!(s.rtype(), RType::List);
+        assert_eq!(s.rtype(), Rtype::List);
         assert_eq!(s.elt(0)?, r!("x"));
         assert_eq!(s.elt(1)?, r!("y"));
         assert_eq!(s.elt(2)?, r!("z"));
@@ -85,11 +85,11 @@ fn test_doubles() {
     test! {
         let s = Doubles::new(10);
         assert_eq!(s.len(), 10);
-        assert_eq!(s.rtype(), RType::Real);
+        assert_eq!(s.rtype(), Rtype::Doubles);
 
         let mut s = Doubles::from_values([1.0, 2.0, 3.0]);
         assert_eq!(s.len(), 3);
-        assert_eq!(s.rtype(), RType::Real);
+        assert_eq!(s.rtype(), Rtype::Doubles);
         assert_eq!(s.elt(0), 1.0);
         assert_eq!(s.elt(1), 2.0);
         assert_eq!(s.elt(2), 3.0);
@@ -134,11 +134,11 @@ fn test_complexes() {
     test! {
         let s = Complexes::new(10);
         assert_eq!(s.len(), 10);
-        assert_eq!(s.rtype(), RType::Complex);
+        assert_eq!(s.rtype(), Rtype::Complexes);
 
         let s = Complexes::from_values([1.0, 2.0, 3.0]);
         assert_eq!(s.len(), 3);
-        assert_eq!(s.rtype(), RType::Complex);
+        assert_eq!(s.rtype(), Rtype::Complexes);
         assert_eq!(s.elt(0), 1.0);
         assert_eq!(s.elt(1), 2.0);
         assert_eq!(s.elt(2), 3.0);
@@ -184,11 +184,11 @@ fn test_integers() {
     test! {
         let s = Integers::new(10);
         assert_eq!(s.len(), 10);
-        assert_eq!(s.rtype(), RType::Integer);
+        assert_eq!(s.rtype(), Rtype::Integers);
 
         let mut s = Integers::from_values([1, 2, 3]);
         assert_eq!(s.len(), 3);
-        assert_eq!(s.rtype(), RType::Integer);
+        assert_eq!(s.rtype(), Rtype::Integers);
         assert_eq!(s.elt(0), 1);
         assert_eq!(s.elt(1), 2);
         assert_eq!(s.elt(2), 3);
diff --git a/tests/extendrtests/tests/testthat/test-type-conversion.R b/tests/extendrtests/tests/testthat/test-type-conversion.R
index d9b067883..913328807 100644
--- a/tests/extendrtests/tests/testthat/test-type-conversion.R
+++ b/tests/extendrtests/tests/testthat/test-type-conversion.R
@@ -56,11 +56,11 @@ test_that("TryFrom conversions work", {
   expect_equal(try_double_vec(c(0, NA_real_)), c(0, NA)) # R type conversion
   expect_equal(try_double_vec(numeric()), numeric())
   expect_false(identical(try_double_vec(NA_real_), NA))
-  expect_error(try_double_vec(c("more", "hooey")), "Expected Real got String")
-  expect_error(try_double_vec(15L), "Expected Real got Integer")
-  expect_error(try_double_vec(TRUE), "Expected Real got Logical")
-  expect_error(try_double_vec(NA), "Expected Real got Logical")
-  expect_error(try_double_vec(NULL), "Expected Real got Null")
+  expect_error(try_double_vec(c("more", "hooey")), "Expected Doubles got String")
+  expect_error(try_double_vec(15L), "Expected Doubles got Integer")
+  expect_error(try_double_vec(TRUE), "Expected Doubles got Logical")
+  expect_error(try_double_vec(NA), "Expected Doubles got Logical")
+  expect_error(try_double_vec(NULL), "Expected Doubles got Null")
 
   # Non-atomic types
   # TODO