Repository: extendr/extendr
Pull request #105 (2020-12-30 23:00:06)

Environments, object access and wrappers
This is an overhaul of wrappers including new environment support.

This also cleans up some protection issues.

Raw vectors now have a distinct `Raw()` wrapper instead of the `&[u8]` conversion before.

Unnamed pair lists have a wrapper.

I plan to do named pair lists and "new" lists also.

I plan to add access functions such as "dollar", "subset" and "index" for $, [ and [[.


clauswilke: This is mostly beyond me but I've looked it over and it seems reasonable. A few more comments in places might be useful.
andy-thomason: Thanks for having a look anyway!

I'm trying to use traits as much as possible which makes it a bit more
complex to read.

For example, using AsRef<str> allows you to use &str, &&str, String,
&&String and so on
as a string parameter.

IntoIterator is a fairly profound thing to support as almost all the Rust
collections generate
it.

I wanted to be able to generate almost any R type, but environments have
proven difficult.
The `R_NewEnv` function seems to be missing from my bindings and so I'm
using a `call!`
instead.

I'm mindful of the namespacing issue. I may allow non-string things in the
call! macro
or something to generate language objects for "mypackage::fred" and
"::mypackage::fred::jim::harry".
(ie. Lang(&[double_colon_symbol(), r!(Symbol("mypackage)),
r!(Symbol("fred"))])

We could do a fast path to known symbols via Symbol, also which bypasses
the conversions:
(ie. Lang(&[Symbol("::"), Symbol("mypackage"), Symbol("fred")])

Some wrappers take integer, real and logical parameters.

Env{parent: global_env(), names: &["a", "b"], values: &[1, 2]}

Obviously it doesn't work if you mix the types:

Env{parent: global_env(), names: &["a", "b"], values: &[1, 1.0]}

You can also use iterators:

Env{parent: global_env(), names: &["a", "b"], values: 1..3}


On Wed, Dec 30, 2020 at 11:26 PM Claus Wilke <notifications@github.com>
wrote:

> This is mostly beyond me but I've looked it over and it seems reasonable.
> A few more comments in places might be useful.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/extendr/extendr/pull/105#issuecomment-752788974>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAL36XCAEY5MZEHLDLZTDTDSXOZK5ANCNFSM4VOZT24A>
> .
>

clauswilke: > The `R_NewEnv` function seems to be missing from my bindings and so I'm using a `call!` instead.

In which header is this included? We don't yet cover all headers in libR-sys and we could include more.

> I'm mindful of the namespacing issue. 

I find the various options you propose all a bit cumbersome. Why not create a macro or struct that can separately take a namespace argument and a function argument, e.g. `lang_namespace!("lm", "stats", y ~ x)` for `stats::lm(y ~ x)`? R doesn't have nested namespaces, so we only ever have one occurrence of `::` in each expression.

andy-thomason: Likewise, we could make call! look for :: and parse it.

call!("stats::lm", y.tilda("x"))

On Thu, Dec 31, 2020 at 12:27 AM Claus Wilke <notifications@github.com>
wrote:

> The R_NewEnv function seems to be missing from my bindings and so I'm
> using a call! instead.
>
> In which header is this included? We don't yet cover all headers in
> libR-sys and we could include more.
>
> I'm mindful of the namespacing issue.
>
> I find the various options you propose all a bit cumbersome. Why not
> create a macro or struct that can separately take a namespace argument and
> a function argument, e.g. lang_namespace!("lm", "stats", y ~ x) for stats::lm(y
> ~ x)? R doesn't have nested namespaces, so we only ever have one
> occurrence of :: in each expression.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/extendr/extendr/pull/105#issuecomment-752800245>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAL36XEW2E632NZCDMDDGOLSXPAPHANCNFSM4VOZT24A>
> .
>

clauswilke: If this can be done at compile time rather than runtime that would be great!
andy-thomason: We are getting close to an R parser at that point!

As a procedural macro, we can do pretty much anything.


On Thu, Dec 31, 2020 at 12:32 AM Claus Wilke <notifications@github.com>
wrote:

> If this can be done at compile time rather than runtime that would be
> great!
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/extendr/extendr/pull/105#issuecomment-752801217>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAL36XEL2GALXJ55TAJJNCTSXPBAFANCNFSM4VOZT24A>
> .
>

clauswilke: I don't understand why `R_NewEnv()` is missing. All the functions around it in the header file are included in the bindings, and I don't see anything about it that is special.
clauswilke: Aha. `R_NewEnv()` is new in R 4.1. Maybe a good reason to support version-specific compilation. I'll look into enabling this in libR-sys.

https://github.com/wch/r-source/commit/8a322e786068ec2e23324e61821ff666d0b6e329

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..a3e6a9163
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,53 @@
+# Changelog
+
+## extendr devel
+
+- Added contributing guidelines and code of conduct.
+
+- Made use of ndarray optional.
+
+- Made #[extendr] calls panic and thread safe.
+
+- Added NA handling to the #[extendr] macro.
+
+- Added a separate extendr-engine crate that is needed when calling R from Rust.
+
+- Wrapper classes for pairlists, environment, raw, symbols and others.
+
+- More iterator support.
+
+- Operators index, slice, dollar, double_colon, +, -, * and /`.
+
+- Debug printing support expanded to use wrappers.
+
+- Conversion of Robj to wrapper types.
+
+- Multithreaded support - allows multithreaded testing using a recursive spinlock.
+
+- Bool type extended and implemented using TRUE, FALSE and NA_BOOLEAN.
+
+- Optional parameters to support NA handing.
+
+- Errors thrown if input parameters without Option are NA.
+
+- Harmonising of function names into integer, real, logical, symbol, raw, list, pairlist and env.
+
+- Refactored robj code into several source files.
+
+- Many functions updated to use generic types.
+
+- R! macro for executing R source code.
+
+- call! macro to call R code.
+
+- sym! macro to generate symbols.
+
+- Simplification of vector generation using collect_robj and ToVectorValue.
+
+- Added array types `[1, 2, 3]` as `Robj::from` targets.
+
+- Macros now mostly return errors.
+
+## extendr 0.1.10
+
+- Fix build on Windows and MacOS.
diff --git a/extendr-api/0001-Add-single_threaded-and-wrap-R-api-writes.patch b/extendr-api/0001-Add-single_threaded-and-wrap-R-api-writes.patch
deleted file mode 100644
index 132dc5ca8..000000000
--- a/extendr-api/0001-Add-single_threaded-and-wrap-R-api-writes.patch
+++ /dev/null
@@ -1,710 +0,0 @@
-From 2e8907f4eb47af5634f4973478159915a076cc2a Mon Sep 17 00:00:00 2001
-From: andy-thomason <andy@andythomason.com>
-Date: Tue, 29 Dec 2020 19:27:34 +0000
-Subject: [PATCH] Add "single_threaded" and wrap R api writes.
-
----
- extendr-api/src/lang.rs            |  23 +++--
- extendr-api/src/lib.rs             |  25 +----
- extendr-api/src/robj/into_robj.rs  | 141 ++++++++++++++++-------------
- extendr-api/src/robj/mod.rs        |  34 +++----
- extendr-api/src/robj/rinternals.rs |  36 ++++----
- extendr-api/src/thread_safety.rs   |  98 ++++++++++++++++++++
- extendr-api/src/wrapper.rs         |  13 +--
- 7 files changed, 233 insertions(+), 137 deletions(-)
- create mode 100644 extendr-api/src/thread_safety.rs
-
-diff --git a/extendr-api/src/lang.rs b/extendr-api/src/lang.rs
-index 1a430adb..ae551230 100644
---- a/extendr-api/src/lang.rs
-+++ b/extendr-api/src/lang.rs
-@@ -4,6 +4,7 @@
- use libR_sys::*;
- //use crate::robj::*;
- use crate::robj::Robj;
-+use crate::single_threaded;
- 
- /// Convert a list of tokens to an array of tuples.
- #[doc(hidden)]
-@@ -42,15 +43,17 @@ macro_rules! args {
- 
- #[doc(hidden)]
- pub unsafe fn append_with_name(tail: SEXP, obj: Robj, name: &str) -> SEXP {
--    let mut name = Vec::from(name.as_bytes());
--    name.push(0);
--    let cons = Rf_cons(obj.get(), R_NilValue);
--    SET_TAG(
--        cons,
--        Rf_install(name.as_ptr() as *const std::os::raw::c_char),
--    );
--    SETCDR(tail, cons);
--    cons
-+    single_threaded(||{
-+        let mut name = Vec::from(name.as_bytes());
-+        name.push(0);
-+        let cons = Rf_cons(obj.get(), R_NilValue);
-+        SET_TAG(
-+            cons,
-+            Rf_install(name.as_ptr() as *const std::os::raw::c_char),
-+        );
-+        SETCDR(tail, cons);
-+        cons
-+    })
- }
- 
- #[doc(hidden)]
-@@ -64,7 +67,7 @@ pub unsafe fn append(tail: SEXP, obj: Robj) -> SEXP {
- pub unsafe fn make_lang(sym: &str) -> Robj {
-     let mut name = Vec::from(sym.as_bytes());
-     name.push(0);
--    let sexp = Rf_lang1(Rf_install(name.as_ptr() as *const std::os::raw::c_char));
-+    let sexp = single_threaded(|| Rf_lang1(Rf_install(name.as_ptr() as *const std::os::raw::c_char)));
-     Robj::from(sexp)
- }
- 
-diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
-index 17027d4a..397df2bb 100644
---- a/extendr-api/src/lib.rs
-+++ b/extendr-api/src/lib.rs
-@@ -147,6 +147,7 @@ mod logical;
- mod rmacros;
- mod robj;
- mod wrapper;
-+mod thread_safety;
- 
- #[cfg(feature = "ndarray")]
- mod robj_ndarray;
-@@ -157,6 +158,7 @@ pub use rmacros::*;
- pub use robj::*;
- pub use wrapper::*;
- pub use logical::*;
-+pub use thread_safety::{single_threaded, handle_panic};
- 
- #[cfg(feature = "ndarray")]
- pub use robj_ndarray::*;
-@@ -215,23 +217,6 @@ pub unsafe fn register_call_methods(info: *mut libR_sys::DllInfo, methods: &[Cal
-     libR_sys::R_forceSymbols(info, 1);
- }
- 
--/// This function is use by the wrapper logic to catch
--/// panics on return.
--#[doc(hidden)]
--pub fn handle_panic<F>(err_str: &str, f: F) -> SEXP
--where
--    F : FnOnce() -> SEXP,
--    F : std::panic::UnwindSafe
--{
--    match std::panic::catch_unwind(f) {
--        Ok(res) => res,
--        Err(_) => {
--            unsafe { libR_sys::Rf_error(err_str.as_ptr() as * const std::os::raw::c_char); }
--            unreachable!("handle_panic unreachable")
--        }
--    }
--}
--
- /// Return true if this primitive is NA.
- pub trait IsNA {
-     fn is_na(&self) -> bool;
-@@ -255,12 +240,6 @@ impl IsNA for Bool {
-     }
- }
- 
--// pub fn add_function_to_namespace(namespace: &str, fn_name: &str, wrap_name: &str) {
--//     let rcode = format!("{}::{} <- function(...) .Call(\"{}\", ...)", namespace, fn_name, wrap_name);
--//     eprintln!("[{}]", rcode);
--//     Robj::eval_string(rcode.as_str()).unwrap();
--// }
--
- #[doc(hidden)]
- pub fn print_r_output<T: Into<Vec<u8>>>(s: T) {
-     let cs = CString::new(s).expect("NulError");
-diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
-index efb104e3..414e7ed1 100644
---- a/extendr-api/src/robj/into_robj.rs
-+++ b/extendr-api/src/robj/into_robj.rs
-@@ -1,4 +1,14 @@
- use super::*;
-+use crate::single_threaded;
-+
-+fn str_to_character(s: &str) -> SEXP {
-q+    unsafe {
-+        Rf_mkCharLen(
-+            s.as_ptr() as *const raw::c_char,
-+            s.len() as i32,
-+        )
-+    }
-+}
- 
- /// Borrow an already protected SEXP
- /// Note that the SEXP must outlive the generated object.
-@@ -19,12 +29,9 @@ impl From<()> for Robj {
- /// Convert a wrapped string ref to an Robj char object.
- impl<'a> From<Character<'a>> for Robj {
-     fn from(val: Character) -> Self {
--        unsafe {
--            new_owned(Rf_mkCharLen(
--                val.0.as_ptr() as *const raw::c_char,
--                val.0.len() as i32,
--            ))
--        }
-+        single_threaded(|| unsafe {
-+            new_owned(str_to_character(val.0))
-+        })
-     }
- }
- 
-@@ -180,8 +187,7 @@ macro_rules! impl_str_tvv {
-             where
-                 Self: Sized,
-             {
--                let s: &str = self.as_ref();
--                unsafe { Rf_mkCharLen(s.as_ptr() as *const raw::c_char, s.len() as i32) }
-+                str_to_character(self.as_ref())
-             }
-         }
- 
-@@ -194,8 +200,7 @@ macro_rules! impl_str_tvv {
-             where
-                 Self: Sized,
-             {
--                let s: &str = self.as_ref();
--                unsafe { Rf_mkCharLen(s.as_ptr() as *const raw::c_char, s.len() as i32) }
-+                str_to_character(self.as_ref())
-             }
-         }
- 
-@@ -209,8 +214,7 @@ macro_rules! impl_str_tvv {
-                 Self: Sized,
-             {
-                 if let Some(s) = self {
--                    let s: &str = s.as_ref();
--                    unsafe { Rf_mkCharLen(s.as_ptr() as *const raw::c_char, s.len() as i32) }
-+                    str_to_character(s.as_ref())
-                 } else {
-                     unsafe { R_NaString }
-                 }
-@@ -314,6 +318,58 @@ impl ToVectorValue for &u8 {
-     }
- }
- 
-+// Not thread safe.
-+unsafe fn fixed_size_collect<I>(iter: I, len: usize) -> Robj
-+where
-+    I: Iterator,
-+    I: Sized,
-+    I::Item: ToVectorValue,
-+{
-+    // Length of the vector is known in advance.
-+    let sexptype = I::Item::sexptype();
-+    if sexptype != 0 {
-+        let sexp = Rf_allocVector(sexptype, len as R_xlen_t);
-+        R_PreserveObject(sexp);
-+        match sexptype {
-+            REALSXP => {
-+                let ptr = REAL(sexp);
-+                for (i, v) in iter.enumerate() {
-+                    *ptr.offset(i as isize) = v.to_real();
-+                }
-+            }
-+            INTSXP => {
-+                let ptr = INTEGER(sexp);
-+                for (i, v) in iter.enumerate() {
-+                    *ptr.offset(i as isize) = v.to_integer();
-+                }
-+            }
-+            LGLSXP => {
-+                let ptr = LOGICAL(sexp);
-+                for (i, v) in iter.enumerate() {
-+                    *ptr.offset(i as isize) = v.to_logical();
-+                }
-+            }
-+            RAWSXP => {
-+                let ptr = RAW(sexp);
-+                for (i, v) in iter.enumerate() {
-+                    *ptr.offset(i as isize) = v.to_raw();
-+                }
-+            }
-+            STRSXP => {
-+                for (i, v) in iter.enumerate() {
-+                    SET_STRING_ELT(sexp, i as isize, v.to_sexp());
-+                }
-+            }
-+            _ => {
-+                panic!("unexpected SEXPTYPE in collect_robj");
-+            }
-+        }
-+        return Robj::Owned(sexp);
-+    } else {
-+        return Robj::from(());
-+    }
-+}
-+
- /// Extensions to iterators for R objects including [RobjItertools::collect_robj()].
- pub trait RobjItertools: Iterator {
-     /// Convert a wide range of iterators to Robj.
-@@ -344,60 +400,17 @@ pub trait RobjItertools: Iterator {
-         Self: Sized,
-         Self::Item: ToVectorValue,
-     {
--        unsafe {
--            if let (len, Some(max)) = self.size_hint().clone() {
--                if len == max {
--                    // Length of the vector is known in advance.
--                    let sexptype = Self::Item::sexptype();
--                    if sexptype != 0 {
--                        let sexp = Rf_allocVector(sexptype, len as R_xlen_t);
--                        R_PreserveObject(sexp);
--                        match sexptype {
--                            REALSXP => {
--                                let ptr = REAL(sexp);
--                                for (i, v) in self.enumerate() {
--                                    *ptr.offset(i as isize) = v.to_real();
--                                }
--                            }
--                            INTSXP => {
--                                let ptr = INTEGER(sexp);
--                                for (i, v) in self.enumerate() {
--                                    *ptr.offset(i as isize) = v.to_integer();
--                                }
--                            }
--                            LGLSXP => {
--                                let ptr = LOGICAL(sexp);
--                                for (i, v) in self.enumerate() {
--                                    *ptr.offset(i as isize) = v.to_logical();
--                                }
--                            }
--                            RAWSXP => {
--                                let ptr = RAW(sexp);
--                                for (i, v) in self.enumerate() {
--                                    *ptr.offset(i as isize) = v.to_raw();
--                                }
--                            }
--                            STRSXP => {
--                                for (i, v) in self.enumerate() {
--                                    SET_STRING_ELT(sexp, i as isize, v.to_sexp());
--                                }
--                            }
--                            _ => {
--                                panic!("unexpected SEXPTYPE in collect_robj");
--                            }
--                        }
--                        return Robj::Owned(sexp);
--                    } else {
--                        return Robj::from(());
--                    }
--                }
-+        if let (len, Some(max)) = self.size_hint().clone() {
-+            if len == max {
-+                return single_threaded(|| unsafe {
-+                    fixed_size_collect(self, len)
-+                });
-             }
--
--            // If the size is indeterminate, create a vector and call recursively.
--            let vec: Vec<_> = self.collect();
--            assert!(vec.iter().size_hint() == (vec.len(), Some(vec.len())));
--            vec.into_iter().collect_robj()
-         }
-+        // If the size is indeterminate, create a vector and call recursively.
-+        let vec: Vec<_> = self.collect();
-+        assert!(vec.iter().size_hint() == (vec.len(), Some(vec.len())));
-+        vec.into_iter().collect_robj()
-     }
- }
- 
-diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
-index d69ea536..d0070c52 100644
---- a/extendr-api/src/robj/mod.rs
-+++ b/extendr-api/src/robj/mod.rs
-@@ -14,7 +14,7 @@ use std::os::raw;
- 
- use crate::logical::*;
- use crate::wrapper::*;
--use crate::{AnyError, IsNA};
-+use crate::{single_threaded, AnyError, IsNA};
- 
- use std::collections::HashMap;
- use std::iter::IntoIterator;
-@@ -596,7 +596,7 @@ impl Robj {
-     ///    assert_eq!(add.eval().unwrap(), r!(3));
-     /// ```
-     pub fn eval(&self) -> Result<Robj, AnyError> {
--        unsafe {
-+        single_threaded(|| unsafe {
-             let mut error: raw::c_int = 0;
-             let res = R_tryEval(self.get(), R_GlobalEnv, &mut error as *mut raw::c_int);
-             if error != 0 {
-@@ -604,7 +604,7 @@ impl Robj {
-             } else {
-                 Ok(Robj::from(res))
-             }
--        }
-+        })
-     }
- 
-     /// Evaluate the expression and return NULL or an R object.
-@@ -631,7 +631,7 @@ impl Robj {
-     ///    assert!(expr.is_expression());
-     /// ```
-     pub fn parse(code: &str) -> Result<Robj, AnyError> {
--        unsafe {
-+        single_threaded(|| unsafe {
-             use libR_sys::*;
-             let mut status = 0_u32;
-             let status_ptr = &mut status as *mut u32;
-@@ -641,7 +641,7 @@ impl Robj {
-                 1 => Ok(parsed),
-                 _ => Err(AnyError::from("parse_error")),
-             }
--        }
-+        })
-     }
- 
-     /// Parse a string into an R executable object and run it.
-@@ -652,14 +652,16 @@ impl Robj {
-     ///    assert_eq!(res, r!(3.));
-     /// ```
-     pub fn eval_string(code: &str) -> Result<Robj, AnyError> {
--        let expr = Robj::parse(code)?;
--        let mut res = Robj::from(());
--        if let Some(iter) = expr.list_iter() {
--            for lang in iter {
--                res = lang.eval()?;
-+        single_threaded(|| {
-+            let expr = Robj::parse(code)?;
-+            let mut res = Robj::from(());
-+            if let Some(iter) = expr.list_iter() {
-+                for lang in iter {
-+                    res = lang.eval()?;
-+                }
-             }
--        }
--        Ok(res)
-+            Ok(res)
-+        })
-     }
- 
-     /// Unprotect an object - assumes a transfer of ownership.
-@@ -668,7 +670,7 @@ impl Robj {
-     pub unsafe fn unprotected(self) -> Robj {
-         match self {
-             Robj::Owned(sexp) => {
--                R_ReleaseObject(sexp);
-+                single_threaded(|| R_ReleaseObject(sexp));
-                 Robj::Borrowed(sexp)
-             }
-             _ => self,
-@@ -806,7 +808,7 @@ impl Robj {
-     {
-         let name = Robj::from(name);
-         let value = Robj::from(value);
--        unsafe { new_borrowed(Rf_setAttrib(self.get(), name.get(), value.get())) }
-+        single_threaded(||unsafe { new_borrowed(Rf_setAttrib(self.get(), name.get(), value.get())) })
-     }
- 
-     /// Get the names attribute as a string iterator if one exists.
-@@ -935,7 +937,7 @@ impl Robj {
- 
- #[doc(hidden)]
- pub unsafe fn new_owned(sexp: SEXP) -> Robj {
--    R_PreserveObject(sexp);
-+    single_threaded(|| R_PreserveObject(sexp));
-     Robj::Owned(sexp)
- }
- 
-@@ -1118,7 +1120,7 @@ impl Drop for Robj {
-     fn drop(&mut self) {
-         unsafe {
-             match self {
--                Robj::Owned(sexp) => R_ReleaseObject(*sexp),
-+                Robj::Owned(sexp) => single_threaded(|| R_ReleaseObject(*sexp)),
-                 Robj::Borrowed(_) => (),
-                 Robj::Sys(_) => (),
-             }
-diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
-index dcf1d7e8..7d0ea7cf 100644
---- a/extendr-api/src/robj/rinternals.rs
-+++ b/extendr-api/src/robj/rinternals.rs
-@@ -68,27 +68,27 @@ impl Robj {
- 
-     /// Convert to vectors of many kinds.
-     pub fn coerce_vector(&self, sexptype: u32) -> Robj {
--        unsafe { new_owned(Rf_coerceVector(self.get(), sexptype as SEXPTYPE)) }
-+        single_threaded(|| unsafe { new_owned(Rf_coerceVector(self.get(), sexptype as SEXPTYPE)) })
-     }
- 
-     /// Convert a pairlist (LISTSXP) to a vector list (VECSXP).
-     pub fn pair_to_vector_list(&self) -> Robj {
--        unsafe { new_owned(Rf_PairToVectorList(self.get())) }
-+        single_threaded(|| unsafe { new_owned(Rf_PairToVectorList(self.get())) })
-     }
- 
-     /// Convert a vector list (VECSXP) to a pair list (LISTSXP)
-     pub fn vector_to_pair_list(&self) -> Robj {
--        unsafe { new_owned(Rf_VectorToPairList(self.get())) }
-+        single_threaded(|| unsafe { new_owned(Rf_VectorToPairList(self.get())) })
-     }
- 
-     /// Convert a factor to a string vector.
-     pub fn as_character_factor(&self) -> Robj {
--        unsafe { new_owned(Rf_asCharacterFactor(self.get())) }
-+        single_threaded(|| unsafe { new_owned(Rf_asCharacterFactor(self.get())) })
-     }
- 
-     /// Allocate a matrix object.
-     pub fn alloc_matrix(sexptype: SEXPTYPE, rows: i32, cols: i32) -> Robj {
--        unsafe { new_owned(Rf_allocMatrix(sexptype, rows, cols)) }
-+        single_threaded(|| unsafe { new_owned(Rf_allocMatrix(sexptype, rows, cols)) })
-     }
- 
-     /* TODO:
-@@ -128,7 +128,7 @@ impl Robj {
-     /// Compatible way to duplicate an object. Use obj.clone() instead
-     /// for Rust compatibility.
-     pub fn duplicate(&self) -> Self {
--        unsafe { new_owned(Rf_duplicate(self.get())) }
-+        single_threaded(|| unsafe { new_owned(Rf_duplicate(self.get())) })
-     }
- 
-     /*
-@@ -168,7 +168,7 @@ impl Robj {
-         if !self.is_environment() {
-             return Err("find_fun needs an environment.".into());
-         }
--        Ok(unsafe { new_borrowed(Rf_findFun(symbol.get(), self.get())) })
-+        Ok(single_threaded(|| unsafe { new_borrowed(Rf_findFun(symbol.get(), self.get())) }))
-     }
- 
-     /*
-@@ -257,11 +257,11 @@ impl Robj {
-     /// Internal function used to implement `#[extendr]` impl
-     #[doc(hidden)]
-     pub unsafe fn make_external_ptr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
--        new_owned(R_MakeExternalPtr(
-+        new_owned(single_threaded(|| R_MakeExternalPtr(
-             p as *mut ::std::os::raw::c_void,
-             tag.get(),
-             prot.get(),
--        ))
-+        )))
-     }
- 
-     #[doc(hidden)]
-@@ -294,12 +294,12 @@ impl Robj {
- 
-     #[doc(hidden)]
-     pub unsafe fn registerCFinalizer(&self, func: R_CFinalizer_t) {
--        self.register_c_finalizer(func)
-+        single_threaded(|| self.register_c_finalizer(func))
-     }
- 
-     #[doc(hidden)]
-     pub unsafe fn register_c_finalizer(&self, func: R_CFinalizer_t) {
--        R_RegisterCFinalizer(self.get(), func);
-+        single_threaded(|| R_RegisterCFinalizer(self.get(), func));
-     }
- 
-     // SEXP R_ExternalPtrTag(SEXP s);
-@@ -350,7 +350,7 @@ impl Robj {
-     pub fn xlengthgets(&self, new_len: usize) -> Result<Robj, AnyError> {
-         unsafe {
-             if self.is_vector() {
--                Ok(new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t)))
-+                Ok(single_threaded(|| new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t))))
-             } else {
-                 Err(AnyError::from("xlengthgets: Not a vector type"))
-             }
-@@ -359,18 +359,18 @@ impl Robj {
- 
-     /// Allocated an owned object of a certain type.
-     pub fn alloc_vector(sexptype: u32, len: usize) -> Robj {
--        unsafe { new_owned(Rf_allocVector(sexptype, len as R_xlen_t)) }
-+        single_threaded(|| unsafe { new_owned(Rf_allocVector(sexptype, len as R_xlen_t)) })
-     }
- 
-     /// Return true if two arrays have identical dims.
-     pub fn conformable(a: &Robj, b: &Robj) -> bool {
--        unsafe { Rf_conformable(a.get(), b.get()) != 0 }
-+        single_threaded(|| unsafe { Rf_conformable(a.get(), b.get()) != 0 })
-     }
- 
-     /// Borrow an element from a list.
--    pub fn elt(&self, index: usize) -> Robj {
--        unsafe { Robj::from(Rf_elt(self.get(), index as raw::c_int)) }
--    }
-+    // pub fn elt(&self, index: usize) -> Robj {
-+    //     single_threaded(|| unsafe { Robj::from(Rf_elt(self.get(), index as raw::c_int)) })
-+    // }
- 
-     //Rboolean Rf_inherits(SEXP, const char *);
- 
-@@ -490,5 +490,5 @@ impl Robj {
- 
- pub fn find_namespace(name: &str) -> Robj {
-     let name = r!(Symbol(name));
--    unsafe { new_borrowed(R_FindNamespace(name.get())) }
-+    single_threaded(|| unsafe { new_borrowed(R_FindNamespace(name.get())) })
- }
-diff --git a/extendr-api/src/thread_safety.rs b/extendr-api/src/thread_safety.rs
-new file mode 100644
-index 00000000..64780f2f
---- /dev/null
-+++ b/extendr-api/src/thread_safety.rs
-@@ -0,0 +1,98 @@
-+//! Provide limited protection for multithreaded access to the R API.
-+
-+use std::cell::RefCell;
-+use std::sync::atomic::{AtomicUsize, Ordering};
-+
-+static OWNER_THREAD: AtomicUsize = AtomicUsize::new(0);
-+static NEXT_THREAD_ID: AtomicUsize = AtomicUsize::new(1);
-+
-+thread_local! {
-+    static THREAD_ID: RefCell<usize> = RefCell::new(0);
-+}
-+
-+// Get an integer 1.. for each thread that calls this.
-+fn this_thread_id() -> usize {
-+    THREAD_ID.with(|f| {
-+        if *f.borrow() == 0 {
-+            // Initialise with next value.
-+            *f.borrow_mut() = NEXT_THREAD_ID.fetch_add(1, Ordering::SeqCst);
-+        }
-+        *f.borrow()
-+    })
-+}
-+
-+/// Run a function single threaded.
-+/// Note: This will fail badly if the called function panics or calls RF_error.
-+///
-+/// ```
-+/// use extendr_api::*;
-+/// use std::sync::atomic::{AtomicUsize, Ordering};
-+/// static GLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);
-+///
-+/// let threads : Vec<_> = (0..100).map(|_| {
-+///    std::thread::spawn(move|| {
-+///       single_threaded(|| {
-+///         // check that we are single threaded.
-+///         let old_thread_count = GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::AcqRel);
-+///         assert_eq!(old_thread_count, 0);
-+///         std::thread::sleep(std::time::Duration::from_millis(1));
-+///         GLOBAL_THREAD_COUNT.fetch_sub(1, Ordering::AcqRel);
-+///         // recursive calls are ok.
-+///         assert_eq!(single_threaded(|| {
-+///           1
-+///         }), 1);    
-+///       })
-+///    })
-+/// }).collect();
-+/// ```
-+pub fn single_threaded<F, R>(f: F) -> R
-+where
-+    F: FnOnce() -> R,
-+{
-+    let id = this_thread_id();
-+    let old_id = OWNER_THREAD.load(Ordering::Acquire);
-+
-+    if old_id == id {
-+        // println!("{:?} id={} RECURSIVE", std::thread::current().name(), id);
-+        // recursive call, don't re-lock.
-+        f()
-+    } else {
-+        // println!("{:?} id={}", std::thread::current().name(), id);
-+
-+        // wait for OWNER_THREAD to become 0 and put us as the owner.
-+        while OWNER_THREAD
-+            .compare_exchange(0, id, Ordering::Acquire, Ordering::Relaxed)
-+            .is_err()
-+        {
-+            std::thread::sleep(std::time::Duration::from_millis(1));
-+        }
-+
-+        // println!("{:?} id={} lock", std::thread::current().name(), id);
-+
-+        let res = f();
-+
-+        // println!("{:?} id={} unlock", std::thread::current().name(), id);
-+
-+        // release the lock.
-+        OWNER_THREAD.store(0, Ordering::Release);
-+        res
-+    }
-+}
-+
-+/// This function is used by the wrapper logic to catch
-+/// panics on return.
-+pub fn handle_panic<F, R>(err_str: &str, f: F) -> R
-+where
-+    F: FnOnce() -> R,
-+    F: std::panic::UnwindSafe,
-+{
-+    match std::panic::catch_unwind(f) {
-+        Ok(res) => res,
-+        Err(_) => {
-+            unsafe {
-+                libR_sys::Rf_error(err_str.as_ptr() as *const std::os::raw::c_char);
-+            }
-+            unreachable!("handle_panic unreachable")
-+        }
-+    }
-+}
-diff --git a/extendr-api/src/wrapper.rs b/extendr-api/src/wrapper.rs
-index 9c378f03..4d339c76 100644
---- a/extendr-api/src/wrapper.rs
-+++ b/extendr-api/src/wrapper.rs
-@@ -6,6 +6,7 @@ use crate::robj::*;
- use libR_sys::*;
- #[doc(hidden)]
- use std::ffi::CString;
-+use crate::single_threaded;
- 
- /// Wrapper for creating symbols.
- ///
-@@ -82,36 +83,36 @@ impl<'a> PartialEq<List<'a>> for Robj {
- impl<'a> From<List<'a>> for Robj {
-     /// Make a list object from an array of Robjs.
-     fn from(val: List<'a>) -> Self {
--        unsafe {
-+        single_threaded(|| unsafe {
-             let sexp = Rf_allocVector(VECSXP, val.0.len() as R_xlen_t);
-             R_PreserveObject(sexp);
-             for i in 0..val.0.len() {
-                 SET_VECTOR_ELT(sexp, i as R_xlen_t, val.0[i].get());
-             }
-             Robj::Owned(sexp)
--        }
-+        })
-     }
- }
- 
- impl<'a> From<Symbol<'a>> for Robj {
-     /// Convert a string to a symbol.
-     fn from(name: Symbol) -> Self {
--        unsafe {
-+        single_threaded(|| unsafe {
-             if let Ok(name) = CString::new(name.0) {
-                 new_owned(Rf_install(name.as_ptr()))
-             } else {
-                 Robj::from(())
-             }
--        }
-+        })
-     }
- }
- 
- impl<'a> From<Lang<'a>> for Robj {
-     /// Convert a wrapped string ref to an Robj language object.
-     fn from(val: Lang<'a>) -> Self {
--        unsafe {
-+        single_threaded(|| unsafe {
-             let name = Robj::from(Symbol(val.0));
-             new_owned(Rf_lang1(name.get()))
--        }
-+        })
-     }
- }
--- 
-2.25.1
-
diff --git a/extendr-api/examples/R/classes/NAMESPACE b/extendr-api/examples/R/classes/NAMESPACE
index b3d60c9a1..4ae07b51b 100644
--- a/extendr-api/examples/R/classes/NAMESPACE
+++ b/extendr-api/examples/R/classes/NAMESPACE
@@ -1,3 +1,3 @@
 
-useDynLib(libclasses, .registration = TRUE)
+useDynLib(classes, .registration = TRUE)
 export(aux_func)
diff --git a/extendr-api/examples/R/classes/src/Makefile b/extendr-api/examples/R/classes/src/Makefile
deleted file mode 100644
index 6924b9f53..000000000
--- a/extendr-api/examples/R/classes/src/Makefile
+++ /dev/null
@@ -1,5 +0,0 @@
-
-all:
-	cargo build --release --manifest-path=../Cargo.toml
-	cp ../target/release/libclasses.so .
-
diff --git a/extendr-api/examples/R/classes/src/Makevars b/extendr-api/examples/R/classes/src/Makevars
new file mode 100644
index 000000000..5f8bd3d84
--- /dev/null
+++ b/extendr-api/examples/R/classes/src/Makevars
@@ -0,0 +1,16 @@
+
+LIBDIR = ../target/release
+STATLIB = $(LIBDIR)/libclasses.a
+PKG_LIBS = -L$(LIBDIR) -lclasses
+
+all: clean
+
+$(SHLIB): $(STATLIB)
+	echo BUILDING SHLIB
+
+$(STATLIB):
+	echo BUILDING STATLIB
+	cargo build --release --manifest-path=../Cargo.toml
+
+clean:
+	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) ../target
diff --git a/extendr-api/examples/R/classes/src/Makevars.win b/extendr-api/examples/R/classes/src/Makevars.win
new file mode 100644
index 000000000..a2a3eae7a
--- /dev/null
+++ b/extendr-api/examples/R/classes/src/Makevars.win
@@ -0,0 +1,14 @@
+TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu
+LIBDIR = ../target/$(TARGET)/release
+STATLIB = $(LIBDIR)/libclasses.a
+PKG_LIBS = -L$(LIBDIR) -lclasses -lws2_32 -ladvapi32 -luserenv
+
+all: clean
+
+$(SHLIB): $(STATLIB)
+
+$(STATLIB):
+	cargo build --target=$(TARGET) --release --manifest-path=../Cargo.toml
+
+clean:
+	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) ../target
diff --git a/extendr-api/examples/R/classes/src/entrypoint.c b/extendr-api/examples/R/classes/src/entrypoint.c
new file mode 100644
index 000000000..9ed55a937
--- /dev/null
+++ b/extendr-api/examples/R/classes/src/entrypoint.c
@@ -0,0 +1,9 @@
+// We need to forward routine registration from C to Rust 
+// to avoid the linker removing the static library.
+
+void R_init_classes_extendr(void *dll);
+
+void R_init_classes(void *dll) {
+  R_init_classes_extendr(dll);
+}
+
diff --git a/extendr-api/examples/R/data/src/lib.rs b/extendr-api/examples/R/data/src/lib.rs
index 2b11b9e09..6001c37f9 100644
--- a/extendr-api/examples/R/data/src/lib.rs
+++ b/extendr-api/examples/R/data/src/lib.rs
@@ -41,7 +41,7 @@ fn data(input: Robj) -> bool {
                     }
                 }
                 "a_list" => {
-                    if let Some(value) = value.list_iter() {
+                    if let Some(value) = value.as_list_iter() {
                         a_list = value.collect::<Vec<_>>();
                     }
                 }
@@ -56,7 +56,7 @@ fn data(input: Robj) -> bool {
                     }
                 }
                 "a_string_array" => {
-                    if let Some(value) = value.str_iter() {
+                    if let Some(value) = value.as_str_iter() {
                         a_string_array = value.map(|s| s.to_string()).collect::<Vec<_>>();
                     }
                 }
diff --git a/extendr-api/examples/R/hello/src/Makevars.win b/extendr-api/examples/R/hello/src/Makevars.win
index ba233e92b..7e45fad50 100644
--- a/extendr-api/examples/R/hello/src/Makevars.win
+++ b/extendr-api/examples/R/hello/src/Makevars.win
@@ -1,6 +1,6 @@
 TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu
 LIBDIR = ../target/$(TARGET)/release
-STATLIB = $(LIBDIR)/hello.lib
+STATLIB = $(LIBDIR)/libhello.a
 PKG_LIBS = -L$(LIBDIR) -lhello -lws2_32 -ladvapi32 -luserenv
 
 all: clean
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index a76770228..224290889 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -21,4 +21,3 @@ pub fn unwrap_or_throw<T>(r: Result<T, &'static str>) -> T {
         }
     }
 }
-
diff --git a/extendr-api/src/lang.rs b/extendr-api/src/lang.rs
index ae551230b..85488d506 100644
--- a/extendr-api/src/lang.rs
+++ b/extendr-api/src/lang.rs
@@ -4,7 +4,7 @@
 use libR_sys::*;
 //use crate::robj::*;
 use crate::robj::Robj;
-use crate::single_threaded;
+use crate::{new_owned, single_threaded};
 
 /// Convert a list of tokens to an array of tuples.
 #[doc(hidden)]
@@ -43,7 +43,7 @@ macro_rules! args {
 
 #[doc(hidden)]
 pub unsafe fn append_with_name(tail: SEXP, obj: Robj, name: &str) -> SEXP {
-    single_threaded(||{
+    single_threaded(|| {
         let mut name = Vec::from(name.as_bytes());
         name.push(0);
         let cons = Rf_cons(obj.get(), R_NilValue);
@@ -67,8 +67,9 @@ pub unsafe fn append(tail: SEXP, obj: Robj) -> SEXP {
 pub unsafe fn make_lang(sym: &str) -> Robj {
     let mut name = Vec::from(sym.as_bytes());
     name.push(0);
-    let sexp = single_threaded(|| Rf_lang1(Rf_install(name.as_ptr() as *const std::os::raw::c_char)));
-    Robj::from(sexp)
+    let sexp =
+        single_threaded(|| Rf_lang1(Rf_install(name.as_ptr() as *const std::os::raw::c_char)));
+    new_owned(sexp)
 }
 
 /// Convert a list of tokens to an array of tuples.
@@ -102,6 +103,7 @@ macro_rules! append_lang {
 ///
 /// let vec = call!("c", 1.0, 2.0, 3.0).unwrap();
 /// assert_eq!(vec, r!([1., 2., 3.]));
+/// assert_eq!(vec.is_owned(), true);
 ///
 /// let list = call!("list", a=1, b=2).unwrap();
 /// assert_eq!(list.len(), 2);
@@ -147,4 +149,3 @@ macro_rules! lang {
         }
     };
 }
-
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 397df2bbe..3ef43ce80 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -9,7 +9,7 @@
 //!
 //! See [Robj] for much of the content of this crate.
 //! [Robj] provides a safe wrapper for the R object type.
-//! 
+//!
 //! Use attributes and macros to export to R.
 //! ```ignore
 //! use extendr_api::*;
@@ -19,12 +19,12 @@
 //!     a + 1
 //! }
 //!
-//! // define exports using extendr_module 
+//! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
 //!    fn fred;    
 //! }
-//! 
+//!
 //! ```
 //!
 //! In R:
@@ -59,6 +59,9 @@
 //!
 //! // A named list using the list! macro.
 //! let list = list!(a = 1, b = 2);
+//!
+//! // A symbol
+//! let sym = sym!(wombat);
 //! ```
 //!
 //! In Rust, we prefer to use iterators rather than loops.
@@ -68,12 +71,17 @@
 //! extendr_engine::start_r(); // Start test environment.
 //!
 //! // 1 ..= 100 is the same as 1:100
-//! let res = (1 ..= 100).collect_robj();
-//! assert_eq!(res.len(), 100);
+//! let res = r!(1 ..= 100);
+//! assert_eq!(res, R!(1:100).unwrap());
 //!
 //! // Rust arrays are zero-indexed so it is more common to use 0 .. 100.
-//! let res = (0 .. 100).collect_robj();
+//! let res = r!(0 .. 100);
 //! assert_eq!(res.len(), 100);
+//!
+//! // Using map is a super fast way to generate vectors.
+//! let iter = (0..3).map(|i| format!("fred{}", i));
+//! let character = iter.collect_robj();
+//! assert_eq!(character, r!(["fred0", "fred1", "fred2"]));
 //! ```
 //!
 //! To index a vector, first convert it to a slice and then
@@ -87,12 +95,47 @@
 //! let slice = vals.as_real_slice().unwrap();
 //! let one = slice[0];
 //! let two = slice[1];
-//! //let error = slice[2];
+//! // let error = slice[2];
+//! assert_eq!(one, 1.0);
+//! assert_eq!(two, 2.0);
+//! ```
+//!
+//! Much slower, but more general are these methods:
 //! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
+//!
+//! let vals = r!([1.0, 2.0, 3.0]);
 //!
-//! The [R!] macro lets you embed R code in Rust.
+//! // one-based indexing [[i]], returns an object.
+//! assert_eq!(vals.index(1).unwrap(), r!(1.0));
 //!
-//! The [r!] macro converts a rust object to an R object.
+//! // one-based slicing [x], returns an object.
+//! assert_eq!(vals.slice(1..=2).unwrap(), r!([1.0, 2.0]));
+//!
+//! // $ operator, returns an object
+//! let list = list!(a = 1.0, b = "xyz");
+//! assert_eq!(list.dollar("a").unwrap(), r!(1.0));
+//! ```
+//!
+//! The [R!] macro lets you embed R code in Rust
+//! but does not take variables.
+//! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
+//! // The text "1+1" is parsed as R source code.
+//! assert_eq!(R!(1+1).unwrap(), r!(2.0));
+//! ```
+//!
+//! The [r!] macro converts a rust object to an R object
+//! and takes parameters.
+//! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
+//! // The text "1.0+1.0" is parsed as Rust source code.
+//! let one = 1.0;
+//! assert_eq!(r!(one+1.0), r!(2.0));
+//! ```
 //!
 //! You can call R functions and primitives using the [call!] macro.
 //! ```
@@ -139,26 +182,28 @@
 //! let slice = robj.as_integer_slice().unwrap();
 //! ```
 
-#![doc(html_logo_url = "https://raw.githubusercontent.com/extendr/extendr/master/extendr-logo-256.png")]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/extendr/extendr/master/extendr-logo-256.png"
+)]
 
-mod lang;
 mod error;
+mod lang;
 mod logical;
 mod rmacros;
 mod robj;
-mod wrapper;
 mod thread_safety;
+mod wrapper;
 
 #[cfg(feature = "ndarray")]
 mod robj_ndarray;
 
-pub use lang::*;
 pub use error::*;
+pub use lang::*;
+pub use logical::*;
 pub use rmacros::*;
 pub use robj::*;
+pub use thread_safety::{handle_panic, single_threaded};
 pub use wrapper::*;
-pub use logical::*;
-pub use thread_safety::{single_threaded, handle_panic};
 
 #[cfg(feature = "ndarray")]
 pub use robj_ndarray::*;
@@ -415,10 +460,8 @@ mod tests {
             );
             wrap__robjtype(Robj::from(1).get());
 
-            // Note u8 is special as it makes the raw type
-            assert_eq!(new_borrowed(wrap__return_u8()), Robj::from(&[123_u8][..]));
-
             // General integer types.
+            assert_eq!(new_borrowed(wrap__return_u8()), Robj::from(123));
             assert_eq!(new_borrowed(wrap__return_u16()), Robj::from(123));
             assert_eq!(new_borrowed(wrap__return_u32()), Robj::from(123));
             assert_eq!(new_borrowed(wrap__return_u64()), Robj::from(123));
@@ -435,7 +478,6 @@ mod tests {
 
     #[test]
     fn r_output_test() {
-        
         // R equivalent
         // > txt_con <- textConnection("test_con", open = "w")
         // > sink(txt_con)
diff --git a/extendr-api/src/logical.rs b/extendr-api/src/logical.rs
index 6abbc3c8a..c8660331d 100644
--- a/extendr-api/src/logical.rs
+++ b/extendr-api/src/logical.rs
@@ -1,7 +1,6 @@
-
 /// Bool is a wrapper for i32 in the context of an R's tristate boolean.
 /// It can be TRUE, FALSE or NA_LOGICAL.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(PartialEq, Eq)]
 pub struct Bool(pub i32);
 
 impl Bool {
@@ -57,3 +56,14 @@ impl From<&Bool> for bool {
         v.0 != 0
     }
 }
+
+impl std::fmt::Debug for Bool {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Bool(0) => write!(f, "FALSE"),
+            Bool(1) => write!(f, "TRUE"),
+            Bool(std::i32::MIN) => write!(f, "NA_LOGICAL"),
+            _ => write!(f, "Bool({})", self.0),
+        }
+    }
+}
diff --git a/extendr-api/src/rmacros.rs b/extendr-api/src/rmacros.rs
index b5e3bd34b..76bf9bb76 100644
--- a/extendr-api/src/rmacros.rs
+++ b/extendr-api/src/rmacros.rs
@@ -49,13 +49,30 @@ macro_rules! r {
 /// }).unwrap();
 /// assert_eq!(x, r!(1.));
 /// ```
-#[macro_export] 
+#[macro_export]
 macro_rules! R {
     ($($t:tt)*) => {
         Robj::eval_string(stringify!($($t)*))
     };
 }
 
+/// The sym! macro install symbols.
+/// You should cache your symbols in variables
+/// as generating them is costly.
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+///
+/// let wombat = sym!(wombat);
+/// assert_eq!(wombat, r!(Symbol("wombat")));
+/// ```
+#[macro_export]
+macro_rules! sym {
+    ($($t:tt)*) => {
+        Robj::from(Symbol(stringify!($($t)*)))
+    };
+}
+
 /// Concatenation operator.
 ///
 /// Example:
@@ -118,7 +135,7 @@ macro_rules! read_table {
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let mylist = list!(x=1, y=2);
-/// assert_eq!(mylist, List(&[1.into(), 2.into()]));
+/// assert_eq!(mylist, r!(List(&[r!(1), r!(2)])));
 /// ```
 ///
 /// Panics on error.
@@ -139,7 +156,7 @@ macro_rules! list {
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let mydata = data_frame!(x=1, y=2);
-/// assert_eq!(mydata, List(&[1.into(), 2.into()]));
+/// assert_eq!(mydata, r!(List(&[r!(1), r!(2)])));
 /// ```
 ///
 /// Panics on error.
@@ -162,7 +179,7 @@ macro_rules! data_frame {
 /// let factor = factor!(vec!["abcd", "def", "fg", "fg"]);
 /// assert_eq!(factor.levels().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg"]);
 /// assert_eq!(factor.as_integer_vector().unwrap(), vec![1, 2, 3, 3]);
-/// assert_eq!(factor.str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
+/// assert_eq!(factor.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
 /// ```
 ///
 /// Panics on error.
diff --git a/extendr-api/src/robj/from_robj.rs b/extendr-api/src/robj/from_robj.rs
index 7a5e0f615..de6feb9ad 100644
--- a/extendr-api/src/robj/from_robj.rs
+++ b/extendr-api/src/robj/from_robj.rs
@@ -133,11 +133,11 @@ macro_rules! impl_iter_from_robj {
     };
 }
 
-impl_iter_from_robj!(StrIter, str_iter, "Not a character vector.");
-impl_iter_from_robj!(VecIter, list_iter, "Not a list.");
-impl_iter_from_robj!(IntegerIter<'a>, integer_iter, "Not an integer vector.");
-impl_iter_from_robj!(RealIter<'a>, real_iter, "Not a real vector.");
-impl_iter_from_robj!(LogicalIter<'a>, logical_iter, "Not a logical vector.");
+impl_iter_from_robj!(StrIter, as_str_iter, "Not a character vector.");
+impl_iter_from_robj!(ListIter, as_list_iter, "Not a list.");
+impl_iter_from_robj!(IntegerIter<'a>, as_integer_iter, "Not an integer vector.");
+impl_iter_from_robj!(RealIter<'a>, as_real_iter, "Not a real vector.");
+impl_iter_from_robj!(LogicalIter<'a>, as_logical_iter, "Not a logical vector.");
 
 /// Pass-through Robj conversion.
 impl<'a> FromRobj<'a> for Robj {
@@ -148,7 +148,7 @@ impl<'a> FromRobj<'a> for Robj {
 
 impl<'a> FromRobj<'a> for HashMap<String, Robj> {
     fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if let Some(iter) = robj.named_list_iter() {
+        if let Some(iter) = robj.as_named_list_iter() {
             Ok(iter
                 .map(|(k, v)| (k.to_string(), v.to_owned()))
                 .collect::<HashMap<String, Robj>>())
@@ -160,7 +160,7 @@ impl<'a> FromRobj<'a> for HashMap<String, Robj> {
 
 impl<'a> FromRobj<'a> for HashMap<&str, Robj> {
     fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if let Some(iter) = robj.named_list_iter() {
+        if let Some(iter) = robj.as_named_list_iter() {
             Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
         } else {
             Err("expected a list")
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index 414e7ed12..43827ea95 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -2,20 +2,7 @@ use super::*;
 use crate::single_threaded;
 
 fn str_to_character(s: &str) -> SEXP {
-    unsafe {
-        Rf_mkCharLen(
-            s.as_ptr() as *const raw::c_char,
-            s.len() as i32,
-        )
-    }
-}
-
-/// Borrow an already protected SEXP
-/// Note that the SEXP must outlive the generated object.
-impl From<SEXP> for Robj {
-    fn from(sexp: SEXP) -> Self {
-        unsafe { new_borrowed(sexp) }
-    }
+    unsafe { Rf_mkCharLen(s.as_ptr() as *const raw::c_char, s.len() as i32) }
 }
 
 /// Convert a null to an Robj.
@@ -29,9 +16,7 @@ impl From<()> for Robj {
 /// Convert a wrapped string ref to an Robj char object.
 impl<'a> From<Character<'a>> for Robj {
     fn from(val: Character) -> Self {
-        single_threaded(|| unsafe {
-            new_owned(str_to_character(val.0))
-        })
+        single_threaded(|| unsafe { new_owned(str_to_character(val.0)) })
     }
 }
 
@@ -84,7 +69,7 @@ pub trait ToVectorValue {
     where
         Self: Sized,
     {
-        unsafe { R_NaString }
+        unsafe { R_NilValue }
     }
 }
 
@@ -172,6 +157,7 @@ impl_integer_tvv!(i8);
 impl_integer_tvv!(i16);
 impl_integer_tvv!(i32);
 impl_integer_tvv!(i64);
+impl_integer_tvv!(u8);
 impl_integer_tvv!(u16);
 impl_integer_tvv!(u32);
 impl_integer_tvv!(u64);
@@ -292,32 +278,6 @@ impl ToVectorValue for Option<bool> {
     }
 }
 
-impl ToVectorValue for u8 {
-    fn sexptype() -> SEXPTYPE {
-        RAWSXP
-    }
-
-    fn to_raw(&self) -> u8
-    where
-        Self: Sized,
-    {
-        *self
-    }
-}
-
-impl ToVectorValue for &u8 {
-    fn sexptype() -> SEXPTYPE {
-        RAWSXP
-    }
-
-    fn to_raw(&self) -> u8
-    where
-        Self: Sized,
-    {
-        **self
-    }
-}
-
 // Not thread safe.
 unsafe fn fixed_size_collect<I>(iter: I, len: usize) -> Robj
 where
@@ -349,12 +309,6 @@ where
                     *ptr.offset(i as isize) = v.to_logical();
                 }
             }
-            RAWSXP => {
-                let ptr = RAW(sexp);
-                for (i, v) in iter.enumerate() {
-                    *ptr.offset(i as isize) = v.to_raw();
-                }
-            }
             STRSXP => {
                 for (i, v) in iter.enumerate() {
                     SET_STRING_ELT(sexp, i as isize, v.to_sexp());
@@ -402,9 +356,7 @@ pub trait RobjItertools: Iterator {
     {
         if let (len, Some(max)) = self.size_hint().clone() {
             if len == max {
-                return single_threaded(|| unsafe {
-                    fixed_size_collect(self, len)
-                });
+                return single_threaded(|| unsafe { fixed_size_collect(self, len) });
             }
         }
         // If the size is indeterminate, create a vector and call recursively.
diff --git a/extendr-api/src/robj/iter.rs b/extendr-api/src/robj/iter.rs
index bba5eca96..4098ae77b 100644
--- a/extendr-api/src/robj/iter.rs
+++ b/extendr-api/src/robj/iter.rs
@@ -1,14 +1,15 @@
 use super::*;
+use std::marker::PhantomData;
 
-// Iterator over the objects in a vector or string.
+// Iterator over the objects in a VECSXP, EXPRSXP or WEAKREFSXP.
 #[derive(Clone)]
-pub struct VecIter {
+pub struct ListIter {
     vector: SEXP,
     i: usize,
     len: usize,
 }
 
-impl Iterator for VecIter {
+impl Iterator for ListIter {
     type Item = Robj;
 
     fn size_hint(&self) -> (usize, Option<usize>) {
@@ -21,7 +22,7 @@ impl Iterator for VecIter {
         if i >= self.len {
             return None;
         } else {
-            Some(Robj::from(unsafe { VECTOR_ELT(self.vector, i as isize) }))
+            Some(unsafe { new_owned(VECTOR_ELT(self.vector, i as isize)) })
         }
     }
 
@@ -31,7 +32,7 @@ impl Iterator for VecIter {
     }
 }
 
-impl std::fmt::Debug for VecIter {
+impl std::fmt::Debug for ListIter {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "[")?;
         for s in self.clone() {
@@ -42,7 +43,7 @@ impl std::fmt::Debug for VecIter {
 }
 
 /// Iterator over name-value pairs in lists.
-pub type NamedListIter = std::iter::Zip<StrIter, VecIter>;
+pub type NamedListIter = std::iter::Zip<StrIter, ListIter>;
 
 /// Iterator over primitives in integer objects.
 pub type IntegerIter<'a> = std::slice::Iter<'a, i32>;
@@ -61,7 +62,7 @@ pub type LogicalIter<'a> = std::slice::Iter<'a, Bool>;
 ///
 /// let my_list = list!(a = 1, b = 2);
 /// let mut total = 0;
-/// for robj in my_list.list_iter().unwrap() {
+/// for robj in my_list.as_list_iter().unwrap() {
 ///   if let Some(val) = robj.as_integer() {
 ///     total += val;
 ///   }
@@ -73,11 +74,11 @@ pub type LogicalIter<'a> = std::slice::Iter<'a, Bool>;
 /// }
 /// ```
 #[derive(Clone)]
-pub struct ListIter {
+pub struct PairlistIter {
     list_elem: SEXP,
 }
 
-impl ListIter {
+impl PairlistIter {
     /// Make an empty list iterator.
     pub fn new() -> Self {
         unsafe {
@@ -88,7 +89,7 @@ impl ListIter {
     }
 }
 
-impl Iterator for ListIter {
+impl Iterator for PairlistIter {
     type Item = Robj;
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -104,7 +105,66 @@ impl Iterator for ListIter {
     }
 }
 
-impl std::fmt::Debug for ListIter {
+impl std::fmt::Debug for PairlistIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "[")?;
+        for s in self.clone() {
+            write!(f, "{:?}", s)?;
+        }
+        write!(f, "]")
+    }
+}
+
+#[derive(Clone)]
+/// Iterator over pairlist tag names.
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
+/// // let mut robj = pairlist!(a = 1, b = 2, 3);
+/// let tags : Vec<_> = robj.as_pairlist_tag_iter().unwrap().collect();
+/// assert_eq!(tags, vec![Some("a"), Some("b"), None]);
+/// ```
+pub struct PairlistTagIter<'a> {
+    list_elem: SEXP,
+    phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> PairlistTagIter<'a> {
+    /// Make an empty list iterator.
+    pub fn new() -> Self {
+        unsafe {
+            Self {
+                list_elem: R_NilValue,
+                phantom: PhantomData,
+            }
+        }
+    }
+}
+
+// 'a is the lifetime of the pairlist.
+impl<'a> Iterator for PairlistTagIter<'a> {
+    type Item = Option<&'a str>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let sexp = self.list_elem;
+            if sexp == R_NilValue {
+                None
+            } else {
+                self.list_elem = CDR(sexp);
+                if let Some(symbol) = new_borrowed::<'a>(TAG(sexp)).as_symbol() {
+                    Some(Some(std::mem::transmute(symbol.0)))
+                } else {
+                    Some(None)
+                }
+            }
+        }
+    }
+}
+
+impl<'a> std::fmt::Debug for PairlistTagIter<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "[")?;
         for s in self.clone() {
@@ -113,6 +173,7 @@ impl std::fmt::Debug for ListIter {
         write!(f, "]")
     }
 }
+
 /// Iterator over strings or string factors.
 ///
 /// ```
@@ -120,13 +181,13 @@ impl std::fmt::Debug for ListIter {
 /// extendr_engine::start_r(); // Start test environment.
 ///
 /// let robj = r!(["a", "b", "c"]);
-/// assert_eq!(robj.str_iter().unwrap().collect::<Vec<_>>(), vec!["a", "b", "c"]);
+/// assert_eq!(robj.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["a", "b", "c"]);
 ///
 /// let factor = factor!(["abcd", "def", "fg", "fg"]);
 /// assert_eq!(factor.levels().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg"]);
 /// assert_eq!(factor.as_integer_vector().unwrap(), vec![1, 2, 3, 3]);
-/// assert_eq!(factor.str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
-/// assert_eq!(factor.str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
+/// assert_eq!(factor.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
+/// assert_eq!(factor.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
 /// ```
 #[derive(Clone)]
 pub struct StrIter {
@@ -207,41 +268,61 @@ impl std::fmt::Debug for StrIter {
 }
 
 impl Robj {
-    /// Get an iterator over a pairlist, lang or ... object.
-    /// Note: we plan to combine list iterators into one.
+    /// Get an iterator over a pairlist objects.
     /// ```
     /// use extendr_api::*;        // Put API in scope.
     /// extendr_engine::start_r(); // Start test environment.
     ///
-    /// let mut robj = lang!("+", 1, 2);
-    /// let objects : Vec<_> = robj.pairlist_iter().unwrap().collect();
-    /// assert_eq!(objects, vec![r!(Symbol("+")), r!(1), r!(2)])
+    /// let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
+    /// let objects : Vec<_> = robj.as_pairlist_iter().unwrap().collect();
+    /// assert_eq!(objects, vec![r!(1.0), r!(2.0), r!(3.0)])
     /// ```
-    pub fn pairlist_iter(&self) -> Option<ListIter> {
+    pub fn as_pairlist_iter(&self) -> Option<PairlistIter> {
         match self.sexptype() {
             LISTSXP | LANGSXP | DOTSXP => unsafe {
-                Some(ListIter {
+                Some(PairlistIter {
+                    list_elem: self.get(),
+                })
+            },
+            _ => None,
+        }
+    }
+
+    /// Get an iterator over a pairlist tags.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
+    /// // let mut robj = pairlist!(a = 1, b = 2, 3);
+    /// let tags : Vec<_> = robj.as_pairlist_tag_iter().unwrap().collect();
+    /// assert_eq!(tags, vec![Some("a"), Some("b"), None]);
+    /// ```
+    pub fn as_pairlist_tag_iter<'a>(&self) -> Option<PairlistTagIter<'a>> {
+        match self.sexptype() {
+            LISTSXP | LANGSXP | DOTSXP => unsafe {
+                Some(PairlistTagIter {
                     list_elem: self.get(),
+                    phantom: PhantomData,
                 })
             },
             _ => None,
         }
     }
 
-    /// Get an iterator over an unnamed list.
-    /// Note: we plan to combine list iterators into one.
+    /// Get an iterator over a list (VECSXP).
     /// ```
     /// use extendr_api::*;        // Put API in scope.
     /// extendr_engine::start_r(); // Start test environment.
     ///
     /// let mut robj = list!(1, 2, 3);
-    /// let objects : Vec<_> = robj.list_iter().unwrap().collect();
+    /// let objects : Vec<_> = robj.as_list_iter().unwrap().collect();
     /// assert_eq!(objects, vec![r!(1), r!(2), r!(3)])
     /// ```
-    pub fn list_iter(&self) -> Option<VecIter> {
+    pub fn as_list_iter(&self) -> Option<ListIter> {
         match self.sexptype() {
             VECSXP | EXPRSXP | WEAKREFSXP => unsafe {
-                Some(VecIter {
+                Some(ListIter {
                     vector: self.get(),
                     i: 0,
                     len: self.len(),
@@ -261,15 +342,15 @@ impl Robj {
     /// extendr_engine::start_r();
     ///
     /// let obj = Robj::from(vec!["a", "b", "c"]);
-    /// assert_eq!(obj.str_iter().unwrap().collect::<Vec<_>>(), vec!["a", "b", "c"]);
+    /// assert_eq!(obj.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["a", "b", "c"]);
     ///
     /// let factor = factor!(vec!["abcd", "def", "fg", "fg"]);
     /// assert_eq!(factor.levels().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg"]);
     /// assert_eq!(factor.as_integer_vector().unwrap(), vec![1, 2, 3, 3]);
-    /// assert_eq!(factor.str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
-    /// assert_eq!(factor.str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
+    /// assert_eq!(factor.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
+    /// assert_eq!(factor.as_str_iter().unwrap().collect::<Vec<_>>(), vec!["abcd", "def", "fg", "fg"]);
     /// ```
-    pub fn str_iter(&self) -> Option<StrIter> {
+    pub fn as_str_iter(&self) -> Option<StrIter> {
         let i = 0;
         let len = self.len();
         match self.sexptype() {
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index d0070c525..661cc2b46 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -23,6 +23,7 @@ use std::ops::{Range, RangeInclusive};
 mod from_robj;
 mod into_robj;
 mod iter;
+mod operators;
 mod rinternals;
 mod symbols;
 
@@ -32,6 +33,7 @@ mod tests;
 pub use from_robj::*;
 pub use into_robj::*;
 pub use iter::*;
+pub use operators::*;
 pub use rinternals::*;
 pub use symbols::*;
 
@@ -80,7 +82,7 @@ pub use symbols::*;
 /// extendr_engine::start_r(); // Start test environment.
 ///
 /// let a : Robj = c!(1, 2, 3, 4, 5);
-/// let iter = a.integer_iter().unwrap();
+/// let iter = a.as_integer_iter().unwrap();
 /// let robj = iter.filter(|&&x| x < 3).collect_robj();
 /// assert_eq!(robj, c!(1, 2));
 /// ```
@@ -105,7 +107,7 @@ pub use symbols::*;
 ///
 /// let abc = list!(a = 1, b = "x", c = vec![1, 2]);
 /// let names : Vec<_> = abc.names().unwrap().collect();
-/// let names_and_values : Vec<_> = abc.named_list_iter().unwrap().collect();
+/// let names_and_values : Vec<_> = abc.as_named_list_iter().unwrap().collect();
 /// assert_eq!(names, vec!["a", "b", "c"]);
 /// assert_eq!(names_and_values, vec![("a", r!(1)), ("b", r!("x")), ("c", r!(vec![1, 2]))]);
 /// ```
@@ -251,12 +253,12 @@ impl Robj {
     ///
     /// let robj = r!([1, 2, 3]);
     /// let mut tot = 0;
-    /// for val in robj.integer_iter().unwrap() {
+    /// for val in robj.as_integer_iter().unwrap() {
     ///   tot += val;
     /// }
     /// assert_eq!(tot, 6);
     /// ```
-    pub fn integer_iter<'a>(&self) -> Option<IntegerIter<'a>>
+    pub fn as_integer_iter<'a>(&self) -> Option<IntegerIter<'a>>
     where
         Self: 'a,
     {
@@ -321,7 +323,7 @@ impl Robj {
     ///
     /// let robj = r!([TRUE, FALSE, NA_LOGICAL]);
     /// let (mut nt, mut nf, mut nna) = (0, 0, 0);
-    /// for val in robj.logical_iter().unwrap() {
+    /// for val in robj.as_logical_iter().unwrap() {
     ///   match *val {
     ///     TRUE => nt += 1,
     ///     FALSE => nf += 1,
@@ -331,7 +333,7 @@ impl Robj {
     /// }
     /// assert_eq!((nt, nf, nna), (1, 1, 1));
     /// ```
-    pub fn logical_iter(&self) -> Option<LogicalIter> {
+    pub fn as_logical_iter(&self) -> Option<LogicalIter> {
         if let Some(slice) = self.as_logical_slice() {
             Some(slice.iter())
         } else {
@@ -366,14 +368,14 @@ impl Robj {
     ///
     /// let robj = r!([1., 2., 3.]);
     /// let mut tot = 0.;
-    /// for val in robj.real_iter().unwrap() {
+    /// for val in robj.as_real_iter().unwrap() {
     ///   if !val.is_na() {
     ///     tot += val;
     ///   }
     /// }
     /// assert_eq!(tot, 6.);
     /// ```
-    pub fn real_iter(&self) -> Option<RealIter> {
+    pub fn as_real_iter(&self) -> Option<RealIter> {
         if let Some(slice) = self.as_real_slice() {
             Some(slice.iter())
         } else {
@@ -402,8 +404,8 @@ impl Robj {
     /// use extendr_api::*;        // Put API in scope.
     /// extendr_engine::start_r(); // Start test environment.
     ///
-    /// let robj = r!([1_u8, 2, 3]);
-    /// assert_eq!(robj.as_raw_slice().unwrap(), [1_u8, 2, 3]);
+    /// let robj = r!(Raw(&[1, 2, 3]));
+    /// assert_eq!(robj.as_raw_slice().unwrap(), &[1, 2, 3]);
     /// ```
     pub fn as_raw_slice(&self) -> Option<&[u8]> {
         self.as_typed_slice()
@@ -444,10 +446,10 @@ impl Robj {
     /// use extendr_api::*;        // Put API in scope.
     /// extendr_engine::start_r(); // Start test environment.
     ///
-    /// let mut robj = r!([1_u8, 2, 3]);
+    /// let mut robj = r!(Raw(&[1, 2, 3]));
     /// let slice = robj.as_raw_slice_mut().unwrap();
     /// slice[1] = 100;
-    /// assert_eq!(robj, r!([1_u8, 100, 3]));
+    /// assert_eq!(robj, r!(Raw(&[1, 100, 3])));
     /// ```
     pub fn as_raw_slice_mut(&mut self) -> Option<&mut [u8]> {
         self.as_typed_slice_mut()
@@ -464,7 +466,7 @@ impl Robj {
     ///    assert_eq!(robj2.as_string_vector(), None);
     /// ```
     pub fn as_string_vector(&self) -> Option<Vec<String>> {
-        if let Some(iter) = self.str_iter() {
+        if let Some(iter) = self.as_str_iter() {
             Some(iter.map(str::to_string).collect())
         } else {
             None
@@ -482,14 +484,14 @@ impl Robj {
     ///    assert_eq!(robj2.as_str_vector(), None);
     /// ```
     pub fn as_str_vector(&self) -> Option<Vec<&str>> {
-        if let Some(iter) = self.str_iter() {
+        if let Some(iter) = self.as_str_iter() {
             Some(iter.collect())
         } else {
             None
         }
     }
 
-    /// Get a read-only reference to a char, symbol or string type.
+    /// Get a read-only reference to a scalar string type.
     /// ```
     ///    use extendr_api::*;
     ///    extendr_engine::start_r();
@@ -498,7 +500,7 @@ impl Robj {
     ///    assert_eq!(robj1.as_str(), Some("xyz"));
     ///    assert_eq!(robj2.as_str(), None);
     /// ```
-    pub fn as_str(&self) -> Option<&str> {
+    pub fn as_str<'a>(&self) -> Option<&'a str> {
         unsafe {
             match self.sexptype() {
                 STRSXP => {
@@ -508,8 +510,8 @@ impl Robj {
                         Some(to_str(R_CHAR(STRING_ELT(self.get(), 0)) as *const u8))
                     }
                 }
-                CHARSXP => Some(to_str(R_CHAR(self.get()) as *const u8)),
-                SYMSXP => Some(to_str(R_CHAR(PRINTNAME(self.get())) as *const u8)),
+                // CHARSXP => Some(to_str(R_CHAR(self.get()) as *const u8)),
+                // SYMSXP => Some(to_str(R_CHAR(PRINTNAME(self.get())) as *const u8)),
                 _ => None,
             }
         }
@@ -602,7 +604,7 @@ impl Robj {
             if error != 0 {
                 Err(AnyError::from("R eval error"))
             } else {
-                Ok(Robj::from(res))
+                Ok(new_owned(res))
             }
         })
     }
@@ -628,7 +630,7 @@ impl Robj {
     ///    use extendr_api::*;
     ///    extendr_engine::start_r();
     ///    let expr = Robj::parse("1 + 2").unwrap();
-    ///    assert!(expr.is_expression());
+    ///    assert!(expr.is_expr());
     /// ```
     pub fn parse(code: &str) -> Result<Robj, AnyError> {
         single_threaded(|| unsafe {
@@ -636,7 +638,7 @@ impl Robj {
             let mut status = 0_u32;
             let status_ptr = &mut status as *mut u32;
             let code: Robj = code.into();
-            let parsed = Robj::from(R_ParseVector(code.get(), -1, status_ptr, R_NilValue));
+            let parsed = new_owned(R_ParseVector(code.get(), -1, status_ptr, R_NilValue));
             match status {
                 1 => Ok(parsed),
                 _ => Err(AnyError::from("parse_error")),
@@ -655,7 +657,7 @@ impl Robj {
         single_threaded(|| {
             let expr = Robj::parse(code)?;
             let mut res = Robj::from(());
-            if let Some(iter) = expr.list_iter() {
+            if let Some(iter) = expr.as_list_iter() {
                 for lang in iter {
                     res = lang.eval()?;
                 }
@@ -808,7 +810,9 @@ impl Robj {
     {
         let name = Robj::from(name);
         let value = Robj::from(value);
-        single_threaded(||unsafe { new_borrowed(Rf_setAttrib(self.get(), name.get(), value.get())) })
+        single_threaded(|| unsafe {
+            new_borrowed(Rf_setAttrib(self.get(), name.get(), value.get()))
+        })
     }
 
     /// Get the names attribute as a string iterator if one exists.
@@ -822,7 +826,7 @@ impl Robj {
     /// ```
     pub fn names(&self) -> Option<StrIter> {
         if let Some(names) = self.get_attrib(names_symbol()) {
-            names.str_iter()
+            names.as_str_iter()
         } else {
             None
         }
@@ -843,7 +847,7 @@ impl Robj {
         Robj: 'a,
     {
         if let Some(dim) = self.get_attrib(dim_symbol()) {
-            dim.integer_iter()
+            dim.as_integer_iter()
         } else {
             None
         }
@@ -858,9 +862,9 @@ impl Robj {
     ///    let names : Vec<_> = array.dimnames().unwrap().collect();
     ///    assert_eq!(names, vec![r!(["x", "y"]), r!(["a", "b"])]);
     /// ```
-    pub fn dimnames(&self) -> Option<VecIter> {
+    pub fn dimnames(&self) -> Option<ListIter> {
         if let Some(names) = self.get_attrib(dimnames_symbol()) {
-            names.list_iter()
+            names.as_list_iter()
         } else {
             None
         }
@@ -872,12 +876,12 @@ impl Robj {
     ///    extendr_engine::start_r();
     ///
     ///    let list = list!(a = 1, b = 2, c = 3);
-    ///    let names_and_values : Vec<_> = list.named_list_iter().unwrap().collect();
+    ///    let names_and_values : Vec<_> = list.as_named_list_iter().unwrap().collect();
     ///    assert_eq!(names_and_values, vec![("a", r!(1)), ("b", r!(2)), ("c", r!(3))]);
     /// ```
-    pub fn named_list_iter(&self) -> Option<NamedListIter> {
+    pub fn as_named_list_iter(&self) -> Option<NamedListIter> {
         if let Some(names) = self.names() {
-            if let Some(values) = self.list_iter() {
+            if let Some(values) = self.as_list_iter() {
                 return Some(names.zip(values));
             }
         }
@@ -895,7 +899,7 @@ impl Robj {
     /// ```
     pub fn class(&self) -> Option<StrIter> {
         if let Some(class) = self.get_attrib(class_symbol()) {
-            class.str_iter()
+            class.as_str_iter()
         } else {
             None
         }
@@ -928,7 +932,7 @@ impl Robj {
     /// ```
     pub fn levels(&self) -> Option<StrIter> {
         if let Some(levels) = self.get_attrib(levels_symbol()) {
-            levels.str_iter()
+            levels.as_str_iter()
         } else {
             None
         }
@@ -942,7 +946,10 @@ pub unsafe fn new_owned(sexp: SEXP) -> Robj {
 }
 
 #[doc(hidden)]
-pub unsafe fn new_borrowed(sexp: SEXP) -> Robj {
+pub unsafe fn new_borrowed<'a>(sexp: SEXP) -> Robj
+where
+    Robj: 'a,
+{
     Robj::Borrowed(sexp)
 }
 
@@ -975,42 +982,46 @@ impl PartialEq<str> for Robj {
 /// Compare equality with two Robjs.
 impl PartialEq<Robj> for Robj {
     fn eq(&self, rhs: &Robj) -> bool {
-        if self.sexptype() == rhs.sexptype() && self.len() == rhs.len() {
-            unsafe {
+        unsafe {
+            if self.get() == rhs.get() {
+                return true;
+            }
+            if self.sexptype() == rhs.sexptype() && self.len() == rhs.len() {
                 let lsexp = self.get();
                 let rsexp = rhs.get();
                 match self.sexptype() {
                     NILSXP => true,
                     SYMSXP => PRINTNAME(lsexp) == PRINTNAME(rsexp),
                     LISTSXP | LANGSXP | DOTSXP => self
-                        .pairlist_iter()
+                        .as_pairlist_iter()
                         .unwrap()
-                        .eq(rhs.pairlist_iter().unwrap()),
+                        .eq(rhs.as_pairlist_iter().unwrap()),
                     CLOSXP => false,
-                    ENVSXP => false,
+                    ENVSXP => self.as_env() == rhs.as_env(),
                     PROMSXP => false,
                     SPECIALSXP => false,
                     BUILTINSXP => false,
-                    CHARSXP => self.as_str() == rhs.as_str(),
+                    CHARSXP => self.as_character() == rhs.as_character(),
                     LGLSXP => self.as_logical_slice() == rhs.as_logical_slice(),
                     INTSXP => self.as_integer_slice() == rhs.as_integer_slice(),
                     REALSXP => self.as_real_slice() == rhs.as_real_slice(),
                     CPLXSXP => false,
                     ANYSXP => false,
-                    VECSXP | EXPRSXP => self.list_iter().unwrap().eq(rhs.list_iter().unwrap()),
-                    STRSXP => self.str_iter().unwrap().eq(rhs.str_iter().unwrap()),
+                    VECSXP | EXPRSXP | WEAKREFSXP => {
+                        self.as_list_iter().unwrap().eq(rhs.as_list_iter().unwrap())
+                    }
+                    STRSXP => self.as_str_iter().unwrap().eq(rhs.as_str_iter().unwrap()),
                     BCODESXP => false,
                     EXTPTRSXP => false,
-                    WEAKREFSXP => false,
                     RAWSXP => self.as_raw_slice() == rhs.as_raw_slice(),
                     S4SXP => false,
                     NEWSXP => false,
                     FREESXP => false,
                     _ => false,
                 }
+            } else {
+                false
             }
-        } else {
-            false
         }
     }
 }
@@ -1019,83 +1030,92 @@ impl PartialEq<Robj> for Robj {
 impl std::fmt::Debug for Robj {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self.sexptype() {
-            NILSXP => write!(f, "NULL"),
-            SYMSXP => write!(f, "Symbol({:?})", self.as_str().unwrap()),
-            // LISTSXP => false,
-            // CLOSXP => false,
-            // ENVSXP => false,
-            // PROMSXP => false,
-            LANGSXP => write!(
-                f,
-                "Lang({:?})",
-                self.pairlist_iter().unwrap().collect::<Vec<Robj>>()
-            ),
-            // SPECIALSXP => false,
-            // BUILTINSXP => false,
-            CHARSXP => write!(f, "Character({:?})", self.as_str().unwrap()),
+            NILSXP => write!(f, "r!(NULL)"),
+            SYMSXP => {
+                if self.is_missing_arg() {
+                    write!(f, "missing_arg()")
+                } else {
+                    write!(f, "sym!({})", self.as_symbol().unwrap().0)
+                }
+            }
+            LISTSXP => write!(f, "r!({:?})", self.as_pairlist().unwrap()),
+            CLOSXP => write!(f, "r!(Function())"),
+            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!({:?})", self.as_env().unwrap())
+                }
+            },
+            PROMSXP => write!(f, "r!(Promise())"),
+            LANGSXP => write!(f, "r!({:?})", self.as_lang().unwrap()),
+            SPECIALSXP => write!(f, "r!(Special())"),
+            BUILTINSXP => write!(f, "r!(Builtin())"),
+            CHARSXP => write!(f, "r!({:?})", self.as_character().unwrap()),
             LGLSXP => {
                 let slice = self.as_logical_slice().unwrap();
                 if slice.len() == 1 {
-                    write!(f, "{}", if slice[0].0 == 0 { "FALSE" } else { "TRUE" })
+                    write!(
+                        f,
+                        "{}",
+                        if slice[0].0 == 0 {
+                            "r!(FALSE)"
+                        } else {
+                            "r!(TRUE)"
+                        }
+                    )
                 } else {
-                    write!(f, "&{:?}", slice)
+                    write!(f, "r!({:?})", slice)
                 }
             }
             INTSXP => {
                 let slice = self.as_integer_slice().unwrap();
                 if slice.len() == 1 {
-                    write!(f, "{:?}", slice[0])
+                    write!(f, "r!({:?})", slice[0])
                 } else {
-                    write!(f, "{:?}", self.as_integer_slice().unwrap())
+                    write!(f, "r!({:?})", self.as_integer_slice().unwrap())
                 }
             }
             REALSXP => {
                 let slice = self.as_real_slice().unwrap();
                 if slice.len() == 1 {
-                    write!(f, "{:?}", slice[0])
+                    write!(f, "r!({:?})", slice[0])
                 } else {
-                    write!(f, "{:?}", slice)
+                    write!(f, "r!({:?})", slice)
                 }
             }
-            VECSXP => write!(f, "{:?}", self.list_iter().unwrap().collect::<Vec<_>>()),
-            EXPRSXP => write!(
-                f,
-                "Expr({:?})",
-                self.list_iter().unwrap().collect::<Vec<_>>()
-            ),
+            VECSXP => write!(f, "r!({:?})", self.as_list().unwrap()),
+            EXPRSXP => write!(f, "r!({:?})", self.as_expr().unwrap()),
             WEAKREFSXP => write!(
                 f,
-                "Weakref({:?})",
-                self.list_iter().unwrap().collect::<Vec<_>>()
+                "r!(Weakref({:?}))",
+                self.as_list_iter().unwrap().collect::<Vec<_>>()
             ),
             // CPLXSXP => false,
             STRSXP => {
-                write!(f, "[")?;
+                write!(f, "r!([")?;
                 let mut sep = "";
-                for obj in self.str_iter().unwrap() {
+                for obj in self.as_str_iter().unwrap() {
                     write!(f, "{}{:?}", sep, obj)?;
                     sep = ", ";
                 }
-                write!(f, "]")
+                write!(f, "])")
             }
-            // DOTSXP => false,
-            // ANYSXP => false,
-            // VECSXP => false,
-            // EXPRSXP => false,
-            // BCODESXP => false,
-            // EXTPTRSXP => false,
-            // WEAKREFSXP => false,
+            DOTSXP => write!(f, "r!(Dot())"),
+            ANYSXP => write!(f, "r!(Any())"),
+            BCODESXP => write!(f, "r!(Bcode())"),
+            EXTPTRSXP => write!(f, "r!(Extptr())"),
             RAWSXP => {
-                let slice = self.as_raw_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "{}", slice[0])
-                } else {
-                    write!(f, "{:?}", slice)
-                }
+                write!(f, "r!({:?})", self.as_raw().unwrap())
             }
-            // S4SXP => false,
-            // NEWSXP => false,
-            // FREESXP => false,
+            S4SXP => write!(f, "r!(S4())"),
+            NEWSXP => write!(f, "r!(New())"),
+            FREESXP => write!(f, "r!(Free())"),
             _ => write!(f, "??"),
         }
     }
@@ -1103,7 +1123,7 @@ impl std::fmt::Debug for Robj {
 
 // Internal utf8 to str conversion.
 // Lets not worry about non-ascii/unicode strings for now (or ever).
-unsafe fn to_str<'a>(ptr: *const u8) -> &'a str {
+pub(crate) unsafe fn to_str<'a>(ptr: *const u8) -> &'a str {
     let mut len = 0;
     loop {
         if *ptr.offset(len) == 0 {
diff --git a/extendr-api/src/robj/operators.rs b/extendr-api/src/robj/operators.rs
new file mode 100644
index 000000000..f1bdb0e30
--- /dev/null
+++ b/extendr-api/src/robj/operators.rs
@@ -0,0 +1,284 @@
+use super::*;
+use crate::*;
+use std::ops::{Add, Div, Mul, Sub};
+
+///////////////////////////////////////////////////////////////
+/// The following impls add operators to Robj.
+///
+impl Robj {
+    /// Do the equivalent of x$y
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let env = r!(Env{
+    ///    parent: global_env(),
+    ///    names_and_values: vec![("a".to_string(), r!(1)), ("b".to_string(), r!(2))]});
+    /// assert_eq!(env.dollar("a").unwrap(), r!(1));
+    /// assert_eq!(env.dollar("b").unwrap(), r!(2));
+    /// ```
+    pub fn dollar<'a, T>(&self, symbol: T) -> Result<Robj, AnyError>
+    where
+        Symbol<'a>: From<T>,
+    {
+        let symbol: Symbol = Symbol::from(symbol);
+        call!("$", self, symbol)
+    }
+
+    /// Do the equivalent of `x[y]`
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let vec = r!([10, 20, 30]);
+    /// assert_eq!(vec.slice(2).unwrap(), r!(20));
+    /// assert_eq!(vec.slice(2..=3).unwrap(), r!([20, 30]));
+    /// ```
+    pub fn slice<T>(&self, rhs: T) -> Result<Robj, AnyError>
+    where
+        T: Into<Robj>,
+    {
+        call!("[", self, rhs.into())
+    }
+
+    /// Do the equivalent of `x[[y]]`
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let vec = r!([10, 20, 30]);
+    /// assert_eq!(vec.index(2).unwrap(), r!(20));
+    /// assert_eq!(vec.index(2..=3).is_err(), true);
+    /// ```
+    pub fn index<T>(&self, rhs: T) -> Result<Robj, AnyError>
+    where
+        T: Into<Robj>,
+    {
+        call!("[[", self, rhs.into())
+    }
+
+    /// Do the equivalent of x ~ y
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let x = r!(Symbol("x"));
+    /// let y = r!(Symbol("y"));
+    /// let tilda = x.tilda(y).unwrap();
+    /// assert_eq!(tilda, r!(Lang(&[r!(Symbol("~")), r!(Symbol("x")), r!(Symbol("y"))])));
+    /// assert_eq!(tilda.inherits("formula"), true);
+    /// ```
+    pub fn tilda<T>(&self, rhs: T) -> Result<Robj, AnyError>
+    where
+        T: Into<Robj>,
+    {
+        call!("~", self, rhs.into())
+    }
+
+    /// Do the equivalent of x :: y
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let base = r!(Symbol("base"));
+    /// let env = r!(Symbol(".getNamespace"));
+    /// let base_env = base.double_colon(env).unwrap();
+    /// assert_eq!(base_env.is_function(), true);
+    /// ```
+    pub fn double_colon<T>(&self, rhs: T) -> Result<Robj, AnyError>
+    where
+        T: Into<Robj>,
+    {
+        call!("::", self, rhs.into())
+    }
+
+    // /// Do the equivalent of x(a, b, c)
+    // /// ```
+    // /// use extendr_api::*;
+    // /// extendr_engine::start_r();
+    // /// let function = R!(function(a, b) a + b).unwrap();
+    // /// assert_eq!(function.is_function(), true);
+    // /// assert_eq!(function.call((1, 2)), r!(3));
+    // /// ```
+    // pub fn call<Args>(&self, _args: Args) -> Robj {
+    //     r!(NULL)
+    // }
+}
+
+impl<Rhs> Add<Rhs> for Robj
+where
+    Rhs: Into<Robj>,
+{
+    type Output = Robj;
+
+    /// Add two R objects, consuming the left hand side.
+    /// panics on error.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    ///
+    /// // lhs and rhs get dropped here
+    /// let lhs = r!([1, 2]);
+    /// let rhs = r!([10, 20]);
+    /// assert_eq!(lhs + rhs, r!([11, 22]));
+    ///
+    /// // lhs gets dropped and rhs is a temporary object.
+    /// let lhs = r!([1, 2]);
+    /// assert_eq!(lhs + 1000, r!([1001, 1002]));
+    ///
+    /// // Only lhs gets dropped.
+    /// let lhs = r!([1, 2]);
+    /// let rhs = r!([10, 20]);
+    /// assert_eq!(lhs + &rhs, r!([11, 22]));
+    /// ```
+    fn add(self, rhs: Rhs) -> Self::Output {
+        call!("+", self, rhs.into()).expect("Robj add failed")
+    }
+}
+
+impl<Rhs> Sub<Rhs> for Robj
+where
+    Rhs: Into<Robj>,
+{
+    type Output = Robj;
+
+    /// Subtract two R objects, consuming the left hand side.
+    /// panics on error.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    ///
+    /// // lhs and rhs get dropped here
+    /// let lhs = r!([10, 20]);
+    /// let rhs = r!([1, 2]);
+    /// assert_eq!(lhs - rhs, r!([9, 18]));
+    ///
+    /// // lhs gets dropped and rhs is a temporary object.
+    /// let lhs = r!([1000, 2000]);
+    /// assert_eq!(lhs - 1, r!([999, 1999]));
+    ///
+    /// // Only lhs gets dropped.
+    /// let lhs = r!([10, 20]);
+    /// let rhs = r!([1, 2]);
+    /// assert_eq!(lhs - &rhs, r!([9, 18]));
+    /// ```
+    fn sub(self, rhs: Rhs) -> Self::Output {
+        call!("-", self, rhs.into()).expect("Robj subtract failed")
+    }
+}
+
+impl<Rhs> Mul<Rhs> for Robj
+where
+    Rhs: Into<Robj>,
+{
+    type Output = Robj;
+
+    /// Multiply two R objects, consuming the left hand side.
+    /// panics on error.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    ///
+    /// // lhs and rhs get dropped here
+    /// let lhs = r!([10.0, 20.0]);
+    /// let rhs = r!([1.0, 2.0]);
+    /// assert_eq!(lhs * rhs, r!([10.0, 40.0]));
+    ///
+    /// // lhs gets dropped and rhs is a temporary object.
+    /// let lhs = r!([1.0, 2.0]);
+    /// assert_eq!(lhs * 10.0, r!([10.0, 20.0]));
+    ///
+    /// // Only lhs gets dropped.
+    /// let lhs = r!([10.0, 20.0]);
+    /// let rhs = r!([1.0, 2.0]);
+    /// assert_eq!(lhs * &rhs, r!([10.0, 40.0]));
+    /// ```
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        call!("*", self, rhs.into()).expect("Robj multiply failed")
+    }
+}
+
+impl<Rhs> Div<Rhs> for Robj
+where
+    Rhs: Into<Robj>,
+{
+    type Output = Robj;
+
+    /// Divide two R objects, consuming the left hand side.
+    /// panics on error.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    ///
+    /// // lhs and rhs get dropped here
+    /// let lhs = r!([10.0, 20.0]);
+    /// let rhs = r!([1.0, 2.0]);
+    /// assert_eq!(lhs / rhs, r!([10.0, 10.0]));
+    ///
+    /// // lhs gets dropped and rhs is a temporary object.
+    /// let lhs = r!([10.0, 30.0]);
+    /// assert_eq!(lhs / 10.0, r!([1.0, 3.0]));
+    ///
+    /// // Only lhs gets dropped.
+    /// let lhs = r!([10.0, 20.0]);
+    /// let rhs = r!([1.0, 2.0]);
+    /// assert_eq!(lhs / &rhs, r!([10.0, 10.0]));
+    /// ```
+    fn div(self, rhs: Rhs) -> Self::Output {
+        call!("/", self, rhs.into()).expect("Robj divide failed")
+    }
+}
+
+// Calls are still experimental.
+//
+// impl<Args> Fn(Args) for Robj
+// {
+//     extern "rust-call" fn call(&self, args: Args) -> Self::Output {
+
+//     }
+// }
+
+/* list of primitives in base.
+> b[sapply(b, function(b) is.primitive(get(b, baseenv())))]
+  [1] "-"               ":"               "!"               "!="
+  [5] "("               "["               "[["              "[[<-"
+  [9] "[<-"             "{"               "@"               "@<-"
+ [13] "*"               "/"               "&"               "&&"
+ [17] "%*%"             "%/%"             "%%"              "^"
+ [21] "+"               "<"               "<-"              "<<-"
+ [25] "<="              "="               "=="              ">"
+ [29] ">="              "|"               "||"              "~"
+ [33] "$"               "$<-"             "abs"             "acos"
+ [37] "acosh"           "all"             "any"             "anyNA"
+ [41] "Arg"             "as.call"         "as.character"    "as.complex"
+ [45] "as.double"       "as.environment"  "as.integer"      "as.logical"
+ [49] "as.numeric"      "as.raw"          "asin"            "asinh"
+ [53] "atan"            "atanh"           "attr"            "attr<-"
+ [57] "attributes"      "attributes<-"    "baseenv"         "break"
+ [61] "browser"         "c"               "call"            "ceiling"
+ [65] "class"           "class<-"         "Conj"            "cos"
+ [69] "cosh"            "cospi"           "cummax"          "cummin"
+ [73] "cumprod"         "cumsum"          "digamma"         "dim"
+ [77] "dim<-"           "dimnames"        "dimnames<-"      "emptyenv"
+ [81] "enc2native"      "enc2utf8"        "environment<-"   "exp"
+ [85] "expm1"           "expression"      "floor"           "for"
+ [89] "forceAndCall"    "function"        "gamma"           "gc.time"
+ [93] "globalenv"       "if"              "Im"              "interactive"
+ [97] "invisible"       "is.array"        "is.atomic"       "is.call"
+[101] "is.character"    "is.complex"      "is.double"       "is.environment"
+[105] "is.expression"   "is.finite"       "is.function"     "is.infinite"
+[109] "is.integer"      "is.language"     "is.list"         "is.logical"
+[113] "is.matrix"       "is.na"           "is.name"         "is.nan"
+[117] "is.null"         "is.numeric"      "is.object"       "is.pairlist"
+[121] "is.raw"          "is.recursive"    "is.single"       "is.symbol"
+[125] "isS4"            "lazyLoadDBfetch" "length"          "length<-"
+[129] "levels<-"        "lgamma"          "list"            "log"
+[133] "log10"           "log1p"           "log2"            "max"
+[137] "min"             "missing"         "Mod"             "names"
+[141] "names<-"         "nargs"           "next"            "nzchar"
+[145] "oldClass"        "oldClass<-"      "on.exit"         "pos.to.env"
+[149] "proc.time"       "prod"            "quote"           "range"
+[153] "Re"              "rep"             "repeat"          "retracemem"
+[157] "return"          "round"           "seq_along"       "seq_len"
+[161] "seq.int"         "sign"            "signif"          "sin"
+[165] "sinh"            "sinpi"           "sqrt"            "standardGeneric"
+[169] "storage.mode<-"  "substitute"      "sum"             "switch"
+[173] "tan"             "tanh"            "tanpi"           "tracemem"
+[177] "trigamma"        "trunc"           "unclass"         "untracemem"
+[181] "UseMethod"       "while"           "xtfrm"
+*/
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 7d0ea7cf4..0450b732a 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -1,10 +1,9 @@
-use crate::*;
 use super::*;
+use crate::*;
 
 ///////////////////////////////////////////////////////////////
 /// The following impls wrap specific Rinternals.h functions.
 ///
-#[allow(non_snake_case)]
 impl Robj {
     /// Return true if this is the null object.
     pub fn is_null(&self) -> bool {
@@ -32,7 +31,7 @@ impl Robj {
     }
 
     /// Return true if this is an expression.
-    pub fn is_expression(&self) -> bool {
+    pub fn is_expr(&self) -> bool {
         unsafe { Rf_isExpression(self.get()) != 0 }
     }
 
@@ -168,7 +167,9 @@ impl Robj {
         if !self.is_environment() {
             return Err("find_fun needs an environment.".into());
         }
-        Ok(single_threaded(|| unsafe { new_borrowed(Rf_findFun(symbol.get(), self.get())) }))
+        Ok(single_threaded(|| unsafe {
+            new_borrowed(Rf_findFun(symbol.get(), self.get()))
+        }))
     }
 
     /*
@@ -250,6 +251,7 @@ impl Robj {
     */
 
     #[doc(hidden)]
+    #[allow(non_snake_case)]
     pub unsafe fn makeExternalPtr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
         Robj::make_external_ptr(p, tag, prot)
     }
@@ -257,14 +259,13 @@ impl Robj {
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
     pub unsafe fn make_external_ptr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
-        new_owned(single_threaded(|| R_MakeExternalPtr(
-            p as *mut ::std::os::raw::c_void,
-            tag.get(),
-            prot.get(),
-        )))
+        new_owned(single_threaded(|| {
+            R_MakeExternalPtr(p as *mut ::std::os::raw::c_void, tag.get(), prot.get())
+        }))
     }
 
     #[doc(hidden)]
+    #[allow(non_snake_case)]
     pub unsafe fn externalPtrAddr<T>(&self) -> *mut T {
         R_ExternalPtrAddr(self.get()) as *mut T
     }
@@ -276,6 +277,7 @@ impl Robj {
     }
 
     #[doc(hidden)]
+    #[allow(non_snake_case)]
     pub unsafe fn externalPtrTag(&self) -> Self {
         self.external_ptr_tag()
     }
@@ -293,6 +295,7 @@ impl Robj {
     }
 
     #[doc(hidden)]
+    #[allow(non_snake_case)]
     pub unsafe fn registerCFinalizer(&self, func: R_CFinalizer_t) {
         single_threaded(|| self.register_c_finalizer(func))
     }
@@ -350,7 +353,9 @@ impl Robj {
     pub fn xlengthgets(&self, new_len: usize) -> Result<Robj, AnyError> {
         unsafe {
             if self.is_vector() {
-                Ok(single_threaded(|| new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t))))
+                Ok(single_threaded(|| {
+                    new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t))
+                }))
             } else {
                 Err(AnyError::from("xlengthgets: Not a vector type"))
             }
@@ -394,18 +399,18 @@ impl Robj {
         unsafe { Rf_isFunction(self.get()) != 0 }
     }
 
-    /// Return true if this is an integer vector.
+    /// Return true if this is an integer vector (INTSXP) but not a factor.
     pub fn is_integer(&self) -> bool {
         unsafe { Rf_isInteger(self.get()) != 0 }
     }
 
-    /// Return true if this is a language object.
+    /// Return true if this is a language object (LANGSXP).
     pub fn is_language(&self) -> bool {
         unsafe { Rf_isLanguage(self.get()) != 0 }
     }
 
-    /// Return true if this is a vector list.
-    pub fn is_list(&self) -> bool {
+    /// Return true if this is NILSXP or LISTSXP.
+    pub fn is_pairlist(&self) -> bool {
         unsafe { Rf_isList(self.get()) != 0 }
     }
 
@@ -414,22 +419,17 @@ impl Robj {
         unsafe { Rf_isMatrix(self.get()) != 0 }
     }
 
-    /// Return true if this is a vector list or null.
-    pub fn is_new_list(&self) -> bool {
+    /// Return true if this is NILSXP or VECSXP.
+    pub fn is_list(&self) -> bool {
         unsafe { Rf_isNewList(self.get()) != 0 }
     }
 
-    /// Return true if this is a numeric vector but not a factor.
+    /// Return true if this is INTSXP, LGLSXP or REALSXP but not a factor.
     pub fn is_number(&self) -> bool {
         unsafe { Rf_isNumber(self.get()) != 0 }
     }
 
-    /// Return true if this is a pairlist.
-    pub fn is_pair_list(&self) -> bool {
-        unsafe { Rf_isPairList(self.get()) != 0 }
-    }
-
-    /// Return true if this is a primitive function.
+    /// Return true if this is a primitive function BUILTINSXP, SPECIALSXP.
     pub fn is_primitive(&self) -> bool {
         unsafe { Rf_isPrimitive(self.get()) != 0 }
     }
@@ -486,9 +486,23 @@ impl Robj {
         }
         false
     }
+
+    pub fn is_missing_arg(&self) -> bool {
+        unsafe { self.get() == R_MissingArg }
+    }
 }
 
-pub fn find_namespace(name: &str) -> Robj {
+///
+/// ```ignore
+///    use extendr_api::*;
+///    extendr_engine::start_r();
+///
+///    println!("{:?}", R!(getNamespace("stats")).unwrap());
+///    // assert_eq!(find_namespace("stats").is_some(), true);
+///    assert!(false);
+/// ```
+pub fn find_namespace(name: &str) -> Option<Robj> {
     let name = r!(Symbol(name));
-    single_threaded(|| unsafe { new_borrowed(R_FindNamespace(name.get())) })
+    let res = single_threaded(|| unsafe { new_borrowed(R_FindNamespace(name.get())) });
+    Some(res)
 }
diff --git a/extendr-api/src/robj/symbols.rs b/extendr-api/src/robj/symbols.rs
index bc393c746..a7c59d298 100644
--- a/extendr-api/src/robj/symbols.rs
+++ b/extendr-api/src/robj/symbols.rs
@@ -15,7 +15,7 @@ pub fn base_env() -> Robj {
     unsafe { new_sys(R_BaseEnv) }
 }
 
-/// The (fake) namespace for base
+/// The namespace for base
 pub fn base_namespace() -> Robj {
     unsafe { new_sys(R_BaseNamespace) }
 }
diff --git a/extendr-api/src/wrapper.rs b/extendr-api/src/wrapper.rs
index 4d339c765..1dff6e576 100644
--- a/extendr-api/src/wrapper.rs
+++ b/extendr-api/src/wrapper.rs
@@ -1,93 +1,194 @@
 //! 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::*;
+use crate::*;
 #[doc(hidden)]
 use libR_sys::*;
 #[doc(hidden)]
-use std::ffi::CString;
-use crate::single_threaded;
+use std::collections::HashMap;
 
 /// Wrapper for creating symbols.
 ///
-/// Example:
 /// ```
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let symbol = r!(Symbol("xyz"));
-/// assert_eq!(symbol.as_str(), Some("xyz"));
+/// assert_eq!(symbol.as_symbol(), Some(Symbol("xyz")));
 /// assert!(symbol.is_symbol());
 /// ```
 /// Note that creating a symbol from a string is expensive
 /// and so you may want to cache them.
 ///
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub struct Symbol<'a>(pub &'a str);
 
 /// Wrapper for creating character objects.
 /// These are used only as the contents of a character
 /// vector.
 ///
-/// Example:
 /// ```
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let chr = r!(Character("xyz"));
-/// assert_eq!(chr.as_str(), Some("xyz"));
+/// assert_eq!(chr.as_character(), Some(Character("xyz")));
 /// ```
 ///
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub struct Character<'a>(pub &'a str);
 
+/// Wrapper for creating raw (byte) objects.
+///
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let bytes = r!(Raw(&[1, 2, 3]));
+/// assert_eq!(bytes.len(), 3);
+/// assert_eq!(bytes.as_raw(), Some(Raw(&[1, 2, 3])));
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct Raw<'a>(pub &'a [u8]);
+
 /// Wrapper for creating language objects.
-/// Example:
 /// ```
 /// use extendr_api::*;
 /// extendr_engine::start_r();
-/// let _call_to_xyz = r!(Lang("xyz"));
+/// let call_to_xyz = r!(Lang(&[r!(Symbol("xyz")), r!(1), r!(2)]));
+/// assert_eq!(call_to_xyz.is_language(), true);
+/// assert_eq!(call_to_xyz.len(), 3);
+/// assert_eq!(call_to_xyz.as_lang(), Some(Lang(vec![r!(Symbol("xyz")), r!(1), r!(2)])));
 /// ```
 ///
-/// Note: prefer to use the [lang!] macro for this.
-#[derive(Debug, PartialEq)]
-pub struct Lang<'a>(pub &'a str);
+/// Note: You can use the [lang!] macro for this.
+#[derive(Debug, PartialEq, Clone)]
+pub struct Lang<T>(pub T);
+
+/// Wrapper for creating pair list (LISTSXP) objects.
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let hashmap : std::collections::HashMap<_, _> = (0..100)
+///     .map(|i| (Some(format!("n{}", i)), r!(i))).collect();
+/// let pairlist = Pairlist{names_and_values: hashmap};
+/// let expr = r!(pairlist);
+/// assert_eq!(expr.len(), 100);
+/// ```
+#[derive(Debug, PartialEq, Clone)]
+pub struct Pairlist<NV> {
+    pub names_and_values: NV,
+}
 
-/// Wrapper for creating list objects.
-/// Example:
+/// Wrapper for creating list (VECSXP) objects.
 /// ```
 /// use extendr_api::*;
 /// extendr_engine::start_r();
-/// let mixed_list = r!(List(&[r!(1.), r!("xyz")]));
-/// assert_eq!(mixed_list.len(), 2);
+/// let list = r!(List(&[r!(0), r!(1), r!(2)]));
+/// assert_eq!(list.is_list(), true);
+/// assert_eq!(list.as_list(), Some(List(vec![r!(0), r!(1), r!(2)])));
+/// assert_eq!(format!("{:?}", list), r#"r!(List([r!(0), r!(1), r!(2)]))"#);
 /// ```
 ///
-/// Note: prefer to use the [list!] macro for this.
-#[derive(Debug, PartialEq)]
-pub struct List<'a>(pub &'a [Robj]);
-
-impl<'a> PartialEq<List<'a>> for Robj {
-    fn eq(&self, rhs: &List) -> bool {
-        match self.sexptype() {
-            VECSXP if self.len() == rhs.0.len() => {
-                for (l, r) in self.list_iter().unwrap().zip(rhs.0.iter()) {
-                    if !l.eq(r) {
-                        return false;
-                    }
-                }
-                true
-            }
-            _ => false,
-        }
-    }
+/// Note: you can use the [list!] macro for named lists.
+#[derive(Debug, PartialEq, Clone)]
+pub struct List<T>(pub T);
+
+/// Wrapper for creating expression objects.
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let expr = r!(Expr(&[r!(1.), r!("xyz")]));
+/// assert_eq!(expr.len(), 2);
+/// ```
+#[derive(Debug, PartialEq, Clone)]
+pub struct Expr<T>(pub T);
+
+/// Wrapper for creating environments.
+#[derive(Debug, PartialEq, Clone)]
+pub struct Env<P, NV> {
+    pub parent: P,
+    pub names_and_values: NV,
+}
+
+/// Wrapper for creating functions (CLOSSXP).
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let expr = R!(function(a = 1, b) {c <- a + b}).unwrap();
+/// let func = expr.as_func().unwrap();
+///
+/// let expected_formals = Pairlist {
+///     names_and_values: vec![(Some("a"), r!(1.0)), (Some("b"), missing_arg())] };
+/// let expected_body = lang!(
+///     "{", lang!("<-", sym!(c), lang!("+", sym!(a), sym!(b))));
+/// assert_eq!(func.formals.as_pairlist().unwrap(), expected_formals);
+/// assert_eq!(func.body, expected_body);
+/// assert_eq!(func.env, global_env());
+/// ```
+#[derive(Debug, PartialEq, Clone)]
+pub struct Func<F, B, E> {
+    pub formals: F,
+    pub body: B,
+    pub env: E,
 }
 
-impl<'a> From<List<'a>> for Robj {
+/// Wrapper for creating and reading Primitive functions.
+///
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let robj = r!(Primitive("+"));
+/// assert!(robj.is_primitive());
+/// assert!(!r!(Primitive("not_a_primitive")).is_primitive());
+/// ```
+#[derive(Debug, PartialEq, Clone)]
+pub struct Primitive<'a>(pub &'a str);
+
+impl<T> From<List<T>> for Robj
+where
+    T: IntoIterator,
+    T::IntoIter: ExactSizeIterator,
+    T::Item: Into<Robj>,
+{
     /// Make a list object from an array of Robjs.
-    fn from(val: List<'a>) -> Self {
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let list_of_ints = r!(List(&[1, 2]));
+    /// assert_eq!(list_of_ints.len(), 2);
+    /// ```
+    fn from(val: List<T>) -> Self {
+        make_vector(VECSXP, val.0)
+    }
+}
+
+impl<T> From<Expr<T>> for Robj
+where
+    T: IntoIterator,
+    T::IntoIter: ExactSizeIterator,
+    T::Item: Into<Robj>,
+{
+    /// Make an expression object from an array of Robjs.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let list_of_ints = r!(Expr(&[1, 2]));
+    /// assert_eq!(list_of_ints.len(), 2);
+    /// ```
+    fn from(val: Expr<T>) -> Self {
+        make_vector(EXPRSXP, val.0)
+    }
+}
+
+impl<'a> From<Raw<'a>> for Robj {
+    /// Make a raw object from bytes.
+    fn from(val: Raw<'a>) -> Self {
         single_threaded(|| unsafe {
-            let sexp = Rf_allocVector(VECSXP, val.0.len() as R_xlen_t);
+            let val = val.0;
+            let sexp = Rf_allocVector(RAWSXP, val.len() as R_xlen_t);
             R_PreserveObject(sexp);
-            for i in 0..val.0.len() {
-                SET_VECTOR_ELT(sexp, i as R_xlen_t, val.0[i].get());
+            let ptr = RAW(sexp);
+            for (i, &v) in val.iter().enumerate() {
+                *ptr.offset(i as isize) = v;
             }
             Robj::Owned(sexp)
         })
@@ -95,24 +196,380 @@ impl<'a> From<List<'a>> for Robj {
 }
 
 impl<'a> From<Symbol<'a>> for Robj {
-    /// Convert a string to a symbol.
+    /// Make a symbol object.
     fn from(name: Symbol) -> Self {
+        single_threaded(|| unsafe { new_owned(make_symbol(name.0)) })
+    }
+}
+
+impl<'a> From<Primitive<'a>> for Robj {
+    /// Make a primitive object, or NULL if not available.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let builtin = r!(Primitive("+"));
+    /// let special = r!(Primitive("if"));
+    /// ```
+    fn from(name: Primitive) -> Self {
         single_threaded(|| unsafe {
-            if let Ok(name) = CString::new(name.0) {
-                new_owned(Rf_install(name.as_ptr()))
+            let sym = make_symbol(name.0);
+            let symvalue = new_sys(SYMVALUE(sym));
+            if symvalue.is_primitive() {
+                symvalue
             } else {
-                Robj::from(())
+                r!(NULL)
             }
         })
     }
 }
 
-impl<'a> From<Lang<'a>> for Robj {
-    /// Convert a wrapped string ref to an Robj language object.
-    fn from(val: Lang<'a>) -> Self {
+impl<T> From<Lang<T>> for Robj
+where
+    T: IntoIterator,
+    T::IntoIter: DoubleEndedIterator,
+    T::Item: Into<Robj>,
+{
+    /// Convert a wrapper to an R language object.
+    fn from(val: Lang<T>) -> Self {
         single_threaded(|| unsafe {
-            let name = Robj::from(Symbol(val.0));
-            new_owned(Rf_lang1(name.get()))
+            let mut res = R_NilValue;
+            let mut num_protected = 0;
+            for val in val.0.into_iter().rev() {
+                let val = Rf_protect(val.into().get());
+                res = Rf_protect(Rf_lcons(val, res));
+                num_protected += 2;
+            }
+            let res = new_owned(res);
+            Rf_unprotect(num_protected);
+            res
         })
     }
 }
+
+impl<'a, P, NV> From<Env<P, NV>> for Robj
+where
+    P: Into<Robj>,
+    NV: IntoIterator + 'a,
+    NV::Item: Into<(String, Robj)>,
+{
+    /// Convert a wrapper to an R environment object.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let hashmap : std::collections::HashMap<_, _> = (0..100)
+    ///     .map(|i| (format!("n{}", i), r!(i))).collect();
+    /// let env = Env{parent: global_env(), names_and_values: hashmap};
+    /// let expr = r!(env);
+    /// assert_eq!(expr.len(), 100);
+    /// ```
+    fn from(val: Env<P, NV>) -> Self {
+        single_threaded(|| {
+            let (parent, names_and_values) = (val.parent, val.names_and_values);
+            let dict_len = 29;
+            let res = call!("new.env", TRUE, parent.into(), dict_len).unwrap();
+            for nv in names_and_values {
+                let (n, v) = nv.into();
+                unsafe { Rf_defineVar(r!(Symbol(n.as_str())).get(), v.get(), res.get()) }
+            }
+            res
+        })
+    }
+}
+
+impl<'a, NV> From<Pairlist<NV>> for Robj
+where
+    NV: IntoIterator + 'a,
+    NV::Item: Into<(Option<String>, Robj)>,
+{
+    /// Convert a wrapper to a LISTSXP object.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let hashmap : std::collections::HashMap<_, _> = (0..100)
+    ///     .map(|i| (Some(format!("n{}", i)), r!(i))).collect();
+    /// let pairlist = Pairlist{names_and_values: hashmap};
+    /// let expr = r!(pairlist);
+    /// assert_eq!(expr.len(), 100);
+    /// ```
+    fn from(val: Pairlist<NV>) -> Self {
+        single_threaded(|| unsafe {
+            let names_and_values = val.names_and_values;
+            let mut num_protects = 0;
+            let mut res = R_NilValue;
+            let names_and_values: Vec<_> = names_and_values.into_iter().collect();
+            for nv in names_and_values.into_iter().rev() {
+                let (name, val) = nv.into();
+                let val = Rf_protect(val.get());
+                res = Rf_protect(Rf_cons(val, res));
+                num_protects += 2;
+                if let Some(name) = name {
+                    let name = r!(Symbol(name.as_str())).get();
+                    SET_TAG(res, name);
+                }
+            }
+            let res = new_owned(res);
+            Rf_unprotect(num_protects as i32);
+            res
+        })
+    }
+}
+
+fn make_symbol(name: &str) -> SEXP {
+    let mut bytes = Vec::with_capacity(name.len() + 1);
+    bytes.extend(name.bytes());
+    bytes.push(0);
+    unsafe { Rf_install(bytes.as_ptr() as *const i8) }
+}
+
+fn make_vector<T>(sexptype: u32, values: T) -> Robj
+where
+    T: IntoIterator,
+    T::IntoIter: ExactSizeIterator,
+    T::Item: Into<Robj>,
+{
+    single_threaded(|| unsafe {
+        let values = values.into_iter();
+        let sexp = Rf_allocVector(sexptype, values.len() as R_xlen_t);
+        R_PreserveObject(sexp);
+        for (i, val) in values.enumerate() {
+            SET_VECTOR_ELT(sexp, i as R_xlen_t, val.into().get());
+        }
+        Robj::Owned(sexp)
+    })
+}
+
+/// Allow you to skip the Symbol() in some cases.
+impl<'a> From<&'a str> for Symbol<'a> {
+    fn from(val: &'a str) -> Self {
+        Self(val)
+    }
+}
+
+impl Robj {
+    /// Convert a symbol object to a Symbol wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let fred = sym!(fred);
+    /// assert_eq!(fred.as_symbol(), Some(Symbol("fred")));
+    /// ```
+    pub fn as_symbol(&self) -> Option<Symbol> {
+        if self.is_symbol() {
+            Some(Symbol(unsafe {
+                to_str(R_CHAR(PRINTNAME(self.get())) as *const u8)
+            }))
+        } else {
+            None
+        }
+    }
+
+    /// Convert a character object to a Character wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let fred = r!(Character("fred"));
+    /// assert_eq!(fred.as_character(), Some(Character("fred")));
+    /// ```
+    pub fn as_character(&self) -> Option<Character> {
+        if self.sexptype() == CHARSXP {
+            Some(Character(unsafe {
+                to_str(R_CHAR(self.get()) as *const u8)
+            }))
+        } else {
+            None
+        }
+    }
+
+    /// Convert a raw object to a Character wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let bytes = r!(Raw(&[1, 2, 3]));
+    /// assert_eq!(bytes.len(), 3);
+    /// assert_eq!(bytes.as_raw(), Some(Raw(&[1, 2, 3])));
+    /// ```
+    pub fn as_raw(&self) -> Option<Raw> {
+        if self.sexptype() == RAWSXP {
+            Some(Raw(self.as_raw_slice().unwrap()))
+        } else {
+            None
+        }
+    }
+    /// Convert a language object to a Lang wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let call_to_xyz = r!(Lang(&[r!(Symbol("xyz")), r!(1), r!(2)]));
+    /// assert_eq!(call_to_xyz.is_language(), true);
+    /// assert_eq!(call_to_xyz.len(), 3);
+    /// assert_eq!(call_to_xyz.as_lang(), Some(Lang(vec![r!(Symbol("xyz")), r!(1), r!(2)])));
+    /// assert_eq!(format!("{:?}", call_to_xyz), r#"r!(Lang([sym!(xyz), r!(1), r!(2)]))"#);
+    /// ```
+    pub fn as_lang(&self) -> Option<Lang<Vec<Robj>>> {
+        if self.sexptype() == LANGSXP {
+            let res: Vec<_> = self
+                .as_pairlist_iter()
+                .unwrap()
+                .map(|robj| robj.to_owned())
+                .collect();
+            Some(Lang(res))
+        } else {
+            None
+        }
+    }
+
+    /// Convert a pair list object (LISTSXP) to a Pairlist wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let names_and_values = vec![(Some("a".to_string()), r!(1)), (Some("b".to_string()), r!(2)), (None, r!(3))];
+    /// let pairlist1 = Pairlist{ names_and_values };
+    /// let names_and_values = vec![(Some("a"), r!(1)), (Some("b"), r!(2)), (None, r!(3))];
+    /// let pairlist2 = Pairlist{ names_and_values };
+    /// let robj = r!(pairlist1);
+    /// assert_eq!(robj.as_pairlist().unwrap(), pairlist2);
+    /// ```
+    pub fn as_pairlist(&self) -> Option<Pairlist<Vec<(Option<&str>, Robj)>>> {
+        if self.sexptype() == LISTSXP {
+            let names = self.as_pairlist_tag_iter().unwrap();
+            let values = self.as_pairlist_iter().unwrap();
+            let names_and_values: Vec<_> = names.zip(values).collect();
+            Some(Pairlist { names_and_values })
+        } else {
+            None
+        }
+    }
+
+    /// Convert a list object (VECSXP) to a List wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let list = r!(List(&[r!(0), r!(1), r!(2)]));
+    /// assert_eq!(list.is_list(), true);
+    /// assert_eq!(list.as_list(), Some(List(vec![r!(0), r!(1), r!(2)])));
+    /// assert_eq!(format!("{:?}", list), r#"r!(List([r!(0), r!(1), r!(2)]))"#);
+    /// ```
+    pub fn as_list(&self) -> Option<List<Vec<Robj>>> {
+        if self.sexptype() == VECSXP {
+            let res: Vec<_> = self
+                .as_list_iter()
+                .unwrap()
+                .map(|robj| robj.to_owned())
+                .collect();
+            Some(List(res))
+        } else {
+            None
+        }
+    }
+
+    /// Convert an expression object (EXPRSXP) to a Expr wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let expr = r!(Expr(&[r!(0), r!(1), r!(2)]));
+    /// assert_eq!(expr.is_expr(), true);
+    /// assert_eq!(expr.as_expr(), Some(Expr(vec![r!(0), r!(1), r!(2)])));
+    /// assert_eq!(format!("{:?}", expr), r#"r!(Expr([r!(0), r!(1), r!(2)]))"#);
+    /// ```
+    pub fn as_expr(&self) -> Option<Expr<Vec<Robj>>> {
+        if self.sexptype() == EXPRSXP {
+            let res: Vec<_> = self
+                .as_list_iter()
+                .unwrap()
+                .map(|robj| robj.to_owned())
+                .collect();
+            Some(Expr(res))
+        } else {
+            None
+        }
+    }
+
+    /// Convert an environment object (ENVSXP) to a Env wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let names_and_values : std::collections::HashMap<_, _> = (0..100).map(|i| (format!("n{}", i), r!(i))).collect();
+    /// let env = Env{parent: global_env(), names_and_values};
+    /// let expr = r!(env.clone());
+    /// assert_eq!(expr.len(), 100);
+    /// let env2 = expr.as_env().unwrap();
+    /// assert_eq!(env2, env);
+    /// ```
+    pub fn as_env(&self) -> Option<Env<Robj, HashMap<String, Robj>>> {
+        if self.sexptype() == ENVSXP {
+            unsafe {
+                let parent = new_owned(ENCLOS(self.get()));
+                let hashtab = new_owned(HASHTAB(self.get()));
+                let frame = new_owned(FRAME(self.get()));
+                let mut names_and_values = HashMap::new();
+                if let Some(as_list_iter) = hashtab.as_list_iter() {
+                    for frame in as_list_iter {
+                        if let (Some(obj_iter), Some(tag_iter)) =
+                            (frame.as_pairlist_iter(), frame.as_pairlist_tag_iter())
+                        {
+                            for (obj, tag) in obj_iter.zip(tag_iter) {
+                                if !obj.is_null() && tag.is_some() {
+                                    names_and_values.insert(tag.unwrap().to_string(), obj);
+                                }
+                            }
+                        }
+                    }
+                } else if let (Some(obj_iter), Some(tag_iter)) =
+                    (frame.as_pairlist_iter(), frame.as_pairlist_tag_iter())
+                {
+                    for (obj, tag) in obj_iter.zip(tag_iter) {
+                        if !obj.is_null() && tag.is_some() {
+                            names_and_values.insert(tag.unwrap().to_string(), obj);
+                        }
+                    }
+                }
+                Some(Env {
+                    parent,
+                    names_and_values,
+                })
+            }
+        } else {
+            None
+        }
+    }
+    /// Convert a function object (CLOSXP) to a Func wrapper.
+    /// ```
+    /// use extendr_api::*;
+    /// extendr_engine::start_r();
+    /// let func = R!(function(a,b) a + b).unwrap();
+    /// println!("{:?}", func.as_func());
+    /// ```
+    pub fn as_func(&self) -> Option<Func<Robj, Robj, Robj>> {
+        if self.sexptype() == CLOSXP {
+            unsafe {
+                let sexp = self.get();
+                let formals = new_owned(FORMALS(sexp));
+                let body = new_owned(BODY(sexp));
+                let env = new_owned(CLOENV(sexp));
+                Some(Func { formals, body, env })
+            }
+        } else {
+            None
+        }
+    }
+
+    // /// Convert a primitive object (BUILTINSXP or SPECIALSXP) to a wrapper.
+    // /// ```
+    // /// use extendr_api::*;
+    // /// extendr_engine::start_r();
+    // /// let builtin = r!(Primitive("+"));
+    // /// let special = r!(Primitive("if"));
+    // /// assert_eq!(builtin.sexptype(), libR_sys::BUILTINSXP);
+    // /// assert_eq!(special.sexptype(), libR_sys::SPECIALSXP);
+    // /// ```
+    // pub fn as_primitive(&self) -> Option<Primitive> {
+    //     match self.sexptype() {
+    //         BUILTINSXP | SPECIALSXP => {
+    //             // Unfortunately, for now PRIMNAME is out of bounds.
+    //             //Some(Primitive(unsafe {to_str(PRIMNAME(self.get()) as * const u8)}))
+    //             None
+    //         }
+    //         _ => None,
+    //     }
+    // }
+}


Test patch:
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index 27201c708..9cf3269bd 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -1,40 +1,37 @@
-use super::*;
+use crate::*;
 
 #[test]
 fn test_debug() {
     extendr_engine::start_r();
     // Special values
-    assert_eq!(format!("{:?}", Robj::from(NULL)), "NULL");
-    assert_eq!(format!("{:?}", Robj::from(TRUE)), "TRUE");
-    assert_eq!(format!("{:?}", Robj::from(FALSE)), "FALSE");
+    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!("{:?}", Robj::from(1)), "1");
-    assert_eq!(format!("{:?}", Robj::from(1.)), "1.0");
-    assert_eq!(format!("{:?}", Robj::from("hello")), "[\"hello\"]");
+    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!("{:?}", Robj::from(s)), "[\"hello\"]");
+    assert_eq!(format!("{:?}", r!(s)), "r!([\"hello\"])");
 
     // Vectors
-    assert_eq!(format!("{:?}", Robj::from([1, 2, 3])), "[1, 2, 3]");
-    assert_eq!(format!("{:?}", Robj::from([1., 2., 3.])), "[1.0, 2.0, 3.0]");
-    assert_eq!(format!("{:?}", Robj::from([1_u8, 2_u8, 3_u8])), "[1, 2, 3]");
+    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(&[1, 2, 3]))), "r!(Raw([1, 2, 3]))");
 
     // Wrappers
-    assert_eq!(format!("{:?}", Robj::from(Symbol("x"))), "Symbol(\"x\")");
+    assert_eq!(format!("{:?}", r!(Symbol("x"))), "sym!(x)");
+    assert_eq!(format!("{:?}", r!(Character("x"))), "r!(Character(\"x\"))");
     assert_eq!(
-        format!("{:?}", Robj::from(Character("x"))),
-        "Character(\"x\")"
-    );
-    assert_eq!(
-        format!("{:?}", Robj::from(Lang("x"))),
-        "Lang([Symbol(\"x\")])"
+        format!("{:?}", r!(Lang(&[r!(Symbol("x"))]))),
+        "r!(Lang([sym!(x)]))"
     );
 
     // Logical
     assert_eq!(
-        format!("{:?}", Robj::from([Bool(1), Bool(0)])),
-        "&[Bool(1), Bool(0)]"
+        format!("{:?}", r!([TRUE, FALSE, NA_LOGICAL])),
+        "r!([TRUE, FALSE, NA_LOGICAL])"
     );
 }
 
@@ -56,45 +53,6 @@ fn test_from_robj() {
     assert_eq!(<Vec::<i32>>::from_robj(&Robj::from(1)), Ok(vec![1]));
     assert_eq!(<Vec::<f64>>::from_robj(&Robj::from(1.)), Ok(vec![1.]));
 
-    /*
-    assert_eq!(
-        <ArrayView1<f64>>::from_robj(&Robj::from(1.)),
-        Ok(ArrayView1::<f64>::from(&[1.][..]))
-    );
-    assert_eq!(
-        <ArrayView1<i32>>::from_robj(&Robj::from(1)),
-        Ok(ArrayView1::<i32>::from(&[1][..]))
-    );
-    assert_eq!(
-        <ArrayView1<Bool>>::from_robj(&Robj::from(true)),
-        Ok(ArrayView1::<Bool>::from(&[Bool(1)][..]))
-    );
-    assert_eq!(
-        <ArrayView2<f64>>::from_robj(&Robj::from(1.)),
-        Ok(ArrayView2::<f64>::from_shape((1, 1), &[1.][..]).unwrap())
-    );
-    assert_eq!(
-        <ArrayView2<i32>>::from_robj(&Robj::from(1)),
-        Ok(ArrayView2::<i32>::from_shape((1, 1), &[1][..]).unwrap())
-    );
-    assert_eq!(
-        <ArrayView2<Bool>>::from_robj(&Robj::from(true)),
-        Ok(ArrayView2::<Bool>::from_shape((1, 1), &[Bool(1)][..]).unwrap())
-    );
-
-    assert_eq!(
-        <ArrayView2<f64>>::from_robj(
-            &Robj::eval_string("matrix(c(1, 2, 3, 4, 5, 6, 7, 8), ncol = 2, nrow = 4, byrow = T)")
-                .unwrap()
-        ),
-        Ok(ArrayView2::<f64>::from_shape(
-            (4, 2),
-            &[1f64, 2f64, 3f64, 4f64, 5f64, 6f64, 7f64, 8f64][..]
-        )
-        .unwrap())
-    );
-    */
-
     let hello = Robj::from("hello");
     assert_eq!(<&str>::from_robj(&hello), Ok("hello"));
 
@@ -185,8 +143,8 @@ 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), "[\"a\", \"b\"]");
-    assert_eq!(format!("{:?}", ab2), "[\"a\", \"b\"]");
+    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());
@@ -209,10 +167,11 @@ fn test_to_robj() {
 fn parse_test() -> Result<(), AnyError> {
     extendr_engine::start_r();
     let p = Robj::parse("print(1L);print(1L);")?;
-    assert_eq!(
-        format!("{:?}", p),
-        "Expr([Lang([Symbol(\"print\"), 1]), Lang([Symbol(\"print\"), 1])])"
-    );
+    let q = r!(Expr(&[
+        r!(Lang(&[r!(Symbol("print")), r!(1)])),
+        r!(Lang(&[r!(Symbol("print")), r!(1)]))
+    ]));
+    assert_eq!(p, q);
 
     let p = Robj::eval_string("1L + 1L")?;
     assert_eq!(p, Robj::from(2));
@@ -280,7 +239,7 @@ fn input_iterator_test() {
 
     let src = &[Robj::from(1), Robj::from(2), Robj::from(3)];
     let robj = Robj::from(List(src));
-    let iter = <VecIter>::from_robj(&robj).unwrap();
+    let iter = <ListIter>::from_robj(&robj).unwrap();
     assert_eq!(iter.collect::<Vec<_>>(), src);
 
     let src: &[i32] = &[1, 2, 3];