Repository: extendr/extendr
Pull request #98 (2020-12-28 19:14:24)

Contributing
Contributing guide and code of conduct, copied from libR-sys.


clauswilke: I accidentally picked up some unrelated commits. Will fix that soon.
clauswilke: @yutannihilation This should be the same as for libR-sys. Would you mind giving it a quick read-through to make sure I didn't mess something up? 

Patch:
diff --git a/.gitignore b/.gitignore
index 409c3fb23..92eec21d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,6 @@
 **/*.so
 .Rproj.user
 **/tasks.json
+
+# CLion support files
+**/.idea
diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md
new file mode 100644
index 000000000..17ad8cd58
--- /dev/null
+++ b/CODE-OF-CONDUCT.md
@@ -0,0 +1,135 @@
+
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+As contributors to and maintainers of this project, we pledge to make participation
+in our community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Project leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Project leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the current maintainers of the project via email. Maintainers are
+identified as Owners on [crates.io](https://crates.io/crates/extendr); email
+addresses are provided in `Cargo.toml`. All complaints will be reviewed and
+investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
+
+Community Impact Guidelines were inspired by 
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available 
+at [https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..1e722a0d6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,69 @@
+# Contributing
+
+We welcome contributions to the extendr project. Contributions come in many forms. Please carefully read and follow these guidelines. This will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing this project.
+
+
+## Quicklinks
+
+* [Code of Conduct](#code-of-conduct)
+* [Getting Started](#getting-started)
+    * [Issues](#issues)
+    * [Pull Requests](#pull-requests)
+* [Getting Help](#getting-help)
+* [Authorship](#authorship)
+* [Attribution](#attribution)
+
+## Code of Conduct
+
+We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct.](https://github.com/extendr/extendr/blob/master/CODE-OF-CONDUCT.md)
+
+## Getting Started
+
+Contributions can be made via Issues and Pull Requests (PRs). A few general guidelines cover both:
+
+- Please search for existing Issues and PRs before creating your own.
+- We work hard to makes sure issues are handled in a timely manner but, depending on the problem and maintainer availability, it could take a while to investigate the problem. A friendly ping in the comment thread can help draw attention if an issue has not received any attention for a while. Please keep in mind that all contributors to this project are volunteers and may have other commitments they need to attend to.
+
+### Issues
+
+Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created. Please **do not** use Issues to request user support.
+
+Whenever possible, please provide a minimal reproducible example (reprex) to any bug report that you are filing. The more minimal your example, the more likely that somebody else can figure out what the problem is, so please remove any code that isn't relevant to the problem you are reporting.
+
+Please keep issues focused on one particular problem. Don't feel shy about opening multiple issues if you're encountering more than one problem.
+
+If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter.
+
+### Pull Requests
+
+PRs are always welcome and can be a quick way to get your fix or improvement slated for the next release. However, please always open an Issue before submitting a PR.
+
+In general, PRs should:
+
+- Address a single concern in the least number of changed lines as possible.
+- Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both.
+- Add unit or integration tests for fixed or changed functionality.
+- Include documentation.
+- Indicate which Issue they address by using the words `Closes #<issue number>` or `Fixes #<issue number>` in the body of the PR and/or the git commit message. (See the [GitHub Documentation](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for details about linking PRs to Issues and automatically closing Issues when merging PRs.)
+
+
+In general, we follow the [GitHub flow](https://guides.github.com/introduction/flow/index.html) development model:
+
+1. Fork the repository to your own Github account
+2. Clone the project to your machine
+3. Create a branch locally with a succinct but descriptive name
+4. Commit changes to the branch
+5. Push changes to your fork
+6. Open a PR in our repository and follow the PR template so that we can efficiently review the changes.
+
+## Getting Help
+
+Please join us on our [Discord server](https://discord.gg/7hmApuc) for general conversations and questions that don't belong into a GitHub issue.
+
+## Authorship
+
+Contributors who have made multiple, sustained, and/or non-trivial contributions to the project may be added to the author list. New author names will always be added at the end of the list, so that author order reflects chronological order of joining the project. All authorship decisions are at the discretion of the current maintainers of the project.
+
+## Attribution
+
+This document was adapted from the [General Contributing Guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) of the auth0 project.
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
new file mode 100644
index 000000000..132dc5ca8
--- /dev/null
+++ b/extendr-api/0001-Add-single_threaded-and-wrap-R-api-writes.patch
@@ -0,0 +1,710 @@
+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/vectors/src/lib.rs b/extendr-api/examples/R/vectors/src/lib.rs
index 98e258f6f..424d3eae7 100644
--- a/extendr-api/examples/R/vectors/src/lib.rs
+++ b/extendr-api/examples/R/vectors/src/lib.rs
@@ -1,5 +1,6 @@
 
 use extendr_api::*;
+#[doc(hidden)]
 use ndarray::prelude::*;
 
 /// Add two vectors using `ArrayView1` and `ArrayViewMut1`.
@@ -14,7 +15,7 @@ fn add1(a: ArrayView1<f64>, b: ArrayView1<f64>) -> Robj {
     let mut result = rep!(0., len);
 
     // Get a mutable view of the result
-    let mut d = ArrayViewMut1::<f64>::from(result.as_f64_slice_mut().expect("must be a numeric vector"));
+    let mut d = ArrayViewMut1::<f64>::from(result.as_numeric_slice_mut().expect("must be a numeric vector"));
 
     // Use azip to add the vectors.
     // Note: this will panic if the vectors are not the same length.
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index a803f01ae..a76770228 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -6,6 +6,7 @@ use std::os::raw;
 static mut R_ERROR_BUF: Vec<u8> = Vec::new();
 
 /// Throw an R error if a result is an error.
+#[doc(hidden)]
 pub fn unwrap_or_throw<T>(r: Result<T, &'static str>) -> T {
     unsafe {
         match r {
diff --git a/extendr-api/src/args.rs b/extendr-api/src/lang.rs
similarity index 62%
rename from extendr-api/src/args.rs
rename to extendr-api/src/lang.rs
index 77926fc0a..ae551230b 100644
--- a/extendr-api/src/args.rs
+++ b/extendr-api/src/lang.rs
@@ -4,8 +4,10 @@
 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)]
 #[macro_export]
 macro_rules! push_args {
     ($args: expr, $name: ident = $val : expr) => {
@@ -24,6 +26,7 @@ macro_rules! push_args {
     };
 }
 
+#[doc(hidden)]
 #[macro_export]
 macro_rules! args {
     () => {
@@ -38,32 +41,38 @@ 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)]
 pub unsafe fn append(tail: SEXP, obj: Robj) -> SEXP {
     let cons = Rf_cons(obj.get(), R_NilValue);
     SETCDR(tail, cons);
     cons
 }
 
+#[doc(hidden)]
 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)
 }
 
 /// Convert a list of tokens to an array of tuples.
+#[doc(hidden)]
 #[macro_export]
 macro_rules! append_lang {
     ($tail: ident, $name: ident = $val : expr) => {
@@ -82,7 +91,45 @@ macro_rules! append_lang {
     };
 }
 
+/// The call! macro calls an R function with Rust parameters.
+/// Equivalent to `lang!(sym, params).eval()`
+/// This returns a Rust Result.
+///
+/// Example:
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+///
+/// let vec = call!("c", 1.0, 2.0, 3.0).unwrap();
+/// assert_eq!(vec, r!([1., 2., 3.]));
+///
+/// let list = call!("list", a=1, b=2).unwrap();
+/// assert_eq!(list.len(), 2);
+///
+/// let three = call!("+", 1, 2).unwrap();
+/// assert_eq!(three, r!(3));
+/// ```
+#[macro_export]
+macro_rules! call {
+    ($($toks: tt)*) => {
+        lang!($($toks)*).eval()
+    }
+}
+
 /// A macro for constructing R langage objects.
+///
+/// Example:
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+///
+/// let call_to_c = lang!("c", 1., 2., 3.);
+/// let vec = call_to_c.eval().unwrap();
+/// assert_eq!(vec, r!([1., 2., 3.]));
+///
+/// let list = lang!("list", a=1, b=2).eval().unwrap();
+/// assert_eq!(list.len(), 2);
+/// ```
 #[macro_export]
 macro_rules! lang {
     ($sym : expr) => {
@@ -101,28 +148,3 @@ macro_rules! lang {
     };
 }
 
-#[cfg(test)]
-mod tests {
-    //use crate::args;
-    use super::*;
-    use extendr_engine::start_r;
-
-    #[test]
-    fn test_args() {
-        start_r();
-        assert_eq!(Robj::from(1).eval().unwrap(), Robj::from(1));
-        //assert_eq!(Robj::from(Lang("ls")), Robj::from(1));
-        assert_eq!(lang!("+", 1, 1).eval().unwrap(), Robj::from(2));
-        assert_eq!(lang!("+", x = 1, y = 1).eval().unwrap(), Robj::from(2));
-        //assert_eq!(Robj::from(Lang("ls")).and(baseenv()).eval().unwrap(), Robj::from(1));
-        //let plus = Robj::from(Lang("+"));
-        /*assert_eq!(args!(), vec![]);
-        assert_eq!(args!(1), vec![("", 1.into())]);
-        assert_eq!(args!(a=1), vec![("a", 1.into())]);
-        assert_eq!(args!(2, a=1), vec![("", 2.into()), ("a", 1.into())]);
-        assert_eq!(args!(1+1), vec![("", Robj::from(2))]);
-        assert_eq!(args!(1+1, 2), [("", Robj::from(2)), ("", Robj::from(2))]);
-        assert_eq!(args!(a=1+1, b=2), [("a", Robj::from(2)), ("b", Robj::from(2))]);*/
-        //end_r();
-    }
-}
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 952178ff3..397df2bbe 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -1,96 +1,186 @@
 //!
-//! extendr - A safe and user friendly R extension interface.
+//! A safe and user friendly R extension interface.
+//!
+//! * Build rust extensions to R.
+//! * Convert R packages to Rust crates.
 //!
 //! This library aims to provide an interface that will be familiar to
 //! first-time users of Rust or indeed any compiled language.
 //!
-//! Anyone who knows the R library should be able to write R extensions.
-//! 
-//! See the [Robj](../robj/enum.Robj.html) struct for much of the content of this crate.
-//! [Robj](../robj/enum.Robj.html) provides a safe wrapper for the R object type.
+//! See [Robj] for much of the content of this crate.
+//! [Robj] provides a safe wrapper for the R object type.
 //! 
-//! This library is just being born, but goals are:
+//! Use attributes and macros to export to R.
+//! ```ignore
+//! use extendr_api::*;
+//! // Export a function or impl to R.
+//! #[extendr]
+//! fn fred(a: i32) -> i32 {
+//!     a + 1
+//! }
 //!
-//! Implement common R functions such as c() and print()
+//! // define exports using extendr_module 
+//! extendr_module! {
+//!    mod mymodule;
+//!    fn fred;    
+//! }
+//! 
+//! ```
 //!
-//! Example:
+//! In R:
 //!
 //! ```ignore
-//! let v = c!(1, 2, 3);
-//! let l = list!(a=1, b=2);
-//! print!(v, l);
+//! result <- fred(1)
 //! ```
 //!
-//! Provide a wrapper for r objects.
+//! [Robj] is a wrapper for R objects.
+//! The r!() and R!() macros let you build R objects
+//! using Rust and R syntax respectively.
+//! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
 //!
-//! Example:
+//! // An R object with a single string "hello"
+//! let character = r!("hello");
 //!
-//! ```ignore
-//! let s = Robj::from("hello");
-//! let i = Robj::from(1);
-//! let r = Robj::from(1.0);
+//! // An R integer object with a single number 1L.
+//! // Note that in Rust, 1 is an integer and 1.0 is a real.
+//! let integer = r!(1);
+//!
+//! // An R real object with a single number 1.
+//! // Note that in R, 1 is a real and 1L is an integer.
+//! let real = r!(1.0);
+//!
+//! // An R real vector.
+//! let real_vector = r!([1.0, 2.0]);
+//!
+//! // An R function object.
+//! let function = R!(function(x, y) { x + y });
+//!
+//! // A named list using the list! macro.
+//! let list = list!(a = 1, b = 2);
 //! ```
 //!
-//! Provide iterator support for creation and consumption of r vectors.
+//! In Rust, we prefer to use iterators rather than loops.
 //!
-//! Example:
+//! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
 //!
-//! ```ignore
-//! let res = (1..=100).iter().collect::<Robj>();
-//! for x in res {
-//!     print!(x);
-//! }
+//! // 1 ..= 100 is the same as 1:100
+//! let res = (1 ..= 100).collect_robj();
+//! assert_eq!(res.len(), 100);
+//!
+//! // Rust arrays are zero-indexed so it is more common to use 0 .. 100.
+//! let res = (0 .. 100).collect_robj();
+//! assert_eq!(res.len(), 100);
+//! ```
+//!
+//! To index a vector, first convert it to a slice and then
+//! remember to use 0-based indexing. In Rust, going out of bounds
+//! will cause and error (a panic) unlike C++ which may crash.
 //! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
 //!
-//! Provide a procedural macro to adapt Rust functions to R
+//! let vals = r!([1.0, 2.0]);
+//! let slice = vals.as_real_slice().unwrap();
+//! let one = slice[0];
+//! let two = slice[1];
+//! //let error = slice[2];
+//! ```
 //!
-//! Example:
+//! The [R!] macro lets you embed R code in Rust.
 //!
-//! ```ignore
-//! #[extendr]
-//! fn fred(a: i32) -> i32 {
-//!     a + 1
-//! }
+//! The [r!] macro converts a rust object to an R object.
+//!
+//! You can call R functions and primitives using the [call!] macro.
 //! ```
 //!
-//! In R:
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
 //!
-//! ```ignore
+//! // As one R! macro call
+//! let confint1 = R!(confint(lm(weight ~ group - 1, PlantGrowth))).unwrap();
 //!
-//! result <- .Call("fred", 1)
+//! // As many parameterized calls.
+//! let formula = R!(weight ~ group - 1).unwrap();
+//! let plant_growth = R!(PlantGrowth).unwrap();
+//! let model = call!("lm", formula, plant_growth).unwrap();
+//! let confint2 = call!("confint", model).unwrap();
 //!
+//! assert_eq!(confint1.as_real_vector(), confint2.as_real_vector());
 //! ```
 //!
+//! Rust has a concept of "Owned" and "Borrowed" objects.
+//!
+//! Owned objects, such as [Vec] and [String] allocate memory
+//! which is released when the object lifetime ends.
+//!
+//! Borrowed objects such as &[i32] and &str are just pointers
+//! to annother object's memory and can't live longer than the
+//! object they reference.
+//!
+//! Borrowed objects are much faster than owned objects and use less
+//! memory but are used only for temporary access.
+//!
+//! When we take a slice of an R vector, for example, we need the
+//! original R object to be alive or the data will be corrupted.
+//!
+//! ```
+//! use extendr_api::*;        // Put API in scope.
+//! extendr_engine::start_r(); // Start test environment.
+//!
+//! // robj is an "Owned" object that controls the memory allocated.
+//! let robj = r!([1, 2, 3]);
+//!
+//! // slice is a "borrowed" reference to the bytes in robj.
+//! // and cannot live longer than robj.
+//! let slice = robj.as_integer_slice().unwrap();
+//! ```
 
-mod args;
+#![doc(html_logo_url = "https://raw.githubusercontent.com/extendr/extendr/master/extendr-logo-256.png")]
+
+mod lang;
 mod error;
 mod logical;
 mod rmacros;
 mod robj;
 mod wrapper;
+mod thread_safety;
 
 #[cfg(feature = "ndarray")]
 mod robj_ndarray;
 
-pub use args::*;
+pub use lang::*;
 pub use error::*;
 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::*;
 
 pub use extendr_macros::*;
+
+#[doc(hidden)]
 pub use libR_sys::DllInfo;
+
+#[doc(hidden)]
 pub use libR_sys::SEXP;
 
+#[doc(hidden)]
 use libR_sys::*;
+
+#[doc(hidden)]
 use std::ffi::CString;
 
 /// Generic dynamic error type.
 pub type AnyError = Box<dyn std::error::Error + Send + Sync>;
 
+#[doc(hidden)]
 pub struct CallMethod {
     pub call_symbol: std::ffi::CString,
     pub func_ptr: *const u8,
@@ -99,6 +189,7 @@ pub struct CallMethod {
 
 // Internal function used to implement the .Call interface.
 // This is called from the code generated by the #[extendr] attribute.
+#[doc(hidden)]
 pub unsafe fn register_call_methods(info: *mut libR_sys::DllInfo, methods: &[CallMethod]) {
     let mut rmethods: Vec<_> = methods
         .iter()
@@ -126,38 +217,30 @@ 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.
-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;
 }
 
-// return true if value is an integer NA in R.
-pub fn integer_is_na(val: i32) -> bool {
-    val == std::i32::MIN
+impl IsNA for f64 {
+    fn is_na(&self) -> bool {
+        unsafe { R_IsNA(*self) != 0 }
+    }
 }
 
-// return true if value is an numeric NA in R.
-pub fn numeric_is_na(val: f64)  -> bool{
-    unsafe { R_IsNA(val) != 0 }
+impl IsNA for i32 {
+    fn is_na(&self) -> bool {
+        *self == std::i32::MIN
+    }
 }
 
-// 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();
-// }
+impl IsNA for Bool {
+    fn is_na(&self) -> bool {
+        self.0 == std::i32::MIN
+    }
+}
 
+#[doc(hidden)]
 pub fn print_r_output<T: Into<Vec<u8>>>(s: T) {
     let cs = CString::new(s).expect("NulError");
     unsafe {
@@ -165,6 +248,7 @@ pub fn print_r_output<T: Into<Vec<u8>>>(s: T) {
     }
 }
 
+#[doc(hidden)]
 pub fn print_r_error<T: Into<Vec<u8>>>(s: T) {
     let cs = CString::new(s).expect("NulError");
     unsafe {
@@ -298,6 +382,7 @@ mod tests {
 
     #[test]
     fn export_test() {
+        extendr_engine::start_r();
         use super::*;
         // Call the exported functions through their generated C wrappers.
         unsafe {
@@ -350,7 +435,7 @@ mod tests {
 
     #[test]
     fn r_output_test() {
-
+        
         // R equivalent
         // > txt_con <- textConnection("test_con", open = "w")
         // > sink(txt_con)
@@ -359,30 +444,14 @@ mod tests {
         // > close(txt_con)
         // > expect_equal(test_con, "Hello world")
         //
-        
-        let con_target_name = "test_con";
-        // `textConnection` outputting to `test_con` R variable
-        let txt_con = 
-            lang!(
-                "textConnection", 
-                Robj::from(con_target_name), 
-                open = Robj::from("w"))
-            .eval().unwrap();
-
-            
-        let txt_con = unsafe { txt_con.get() };
-        // `sink` to connection
-        lang!("sink", txt_con).eval_blind();
+
+        extendr_engine::start_r();
+        let txt_con = R!(textConnection("test_con", open = "w")).unwrap();
+        call!("sink", &txt_con).unwrap();
         rprintln!("Hello world");
-        // `sink` reset
-        lang!("sink").eval_blind();
-        
-        // Retrieve value of `test_con` by evaluating R variable name
-        let result = Robj::eval_string(&con_target_name).unwrap();
-        
-        // Close connection
-        lang!("close", txt_con).eval_blind();
-        
-        assert_eq!(result, Robj::from("Hello world"));
+        call!("sink").unwrap();
+        call!("close", &txt_con).unwrap();
+        let result = R!(test_con).unwrap();
+        assert_eq!(result, r!("Hello world"));
     }
 }
diff --git a/extendr-api/src/logical.rs b/extendr-api/src/logical.rs
index d78ad18bf..6abbc3c8a 100644
--- a/extendr-api/src/logical.rs
+++ b/extendr-api/src/logical.rs
@@ -1,28 +1,28 @@
 
-use super::integer_is_na;
 /// Bool is a wrapper for i32 in the context of an R's tristate boolean.
-#[derive(Debug)]
+/// It can be TRUE, FALSE or NA_LOGICAL.
+#[derive(Debug, PartialEq, Eq)]
 pub struct Bool(pub i32);
 
 impl Bool {
-    // Convert this Bool to a bool. Note NA will be true.
+    /// Convert this Bool to a bool. Note NA will be true.
     pub fn to_bool(&self) -> bool {
         self.0 != 0
     }
 
-    // Convert this construct a Bool from a rust boolean.
+    /// Convert this construct a Bool from a rust boolean.
     pub fn from_bool(val: bool) -> Self {
         Bool(val as i32)
     }
 
-    // Return true if this Bool is NA.
-    pub fn is_na(&self) -> bool {
-        integer_is_na(self.0)
+    /// Test if TRUE
+    pub fn is_true(&self) -> bool {
+        self.0 != 0
     }
 
-    // Construct a NA Bool.
-    pub fn na() -> Bool {
-        Bool(std::i32::MIN)
+    /// Test if FALSE
+    pub fn is_false(&self) -> bool {
+        self.0 == 0
     }
 }
 
@@ -57,9 +57,3 @@ impl From<&Bool> for bool {
         v.0 != 0
     }
 }
-
-impl PartialEq<Bool> for Bool {
-    fn eq(&self, rhs: &Bool) -> bool {
-        self.0 == rhs.0
-    }
-}
diff --git a/extendr-api/src/rmacros.rs b/extendr-api/src/rmacros.rs
index 73f6d3197..b5e3bd34b 100644
--- a/extendr-api/src/rmacros.rs
+++ b/extendr-api/src/rmacros.rs
@@ -2,7 +2,7 @@
 //! rmacros - a set of macros to call actual R functions in a rusty way.
 //!
 
-/// R object encapsulation operator.
+/// Convert a rust expression to an R object.
 ///
 /// Shorthand for Robj::from(x).
 ///
@@ -12,6 +12,19 @@
 /// extendr_engine::start_r();
 /// let fred = r!(1);
 /// assert_eq!(fred, Robj::from(1));
+///
+/// let int_array = r!([1, 2, 3]);
+/// assert_eq!(int_array.len(), 3);
+///
+/// let numeric_array = r!([1., 2., 3.]);
+/// assert_eq!(numeric_array.len(), 3);
+///
+/// let logical_array = r!([true, false, true]);
+/// assert_eq!(logical_array.len(), 3);
+///
+/// let numeric_array_with_na = r!([Some(1.), None, Some(3.)]);
+/// assert_eq!(numeric_array_with_na.len(), 3);
+///
 /// ```
 #[macro_export]
 macro_rules! r {
@@ -20,6 +33,29 @@ macro_rules! r {
     };
 }
 
+/// Call inline R source code from Rust.
+///
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+///
+/// let formula = R!(y ~ z + 1).unwrap();
+/// assert!(formula.inherits("formula"));
+///
+/// // Currently, multiline calls require semicolons as the newlines are lost.
+/// let x = R!({
+///    x <- function() { 1 };
+///    x()
+/// }).unwrap();
+/// assert_eq!(x, r!(1.));
+/// ```
+#[macro_export] 
+macro_rules! R {
+    ($($t:tt)*) => {
+        Robj::eval_string(stringify!($($t)*))
+    };
+}
+
 /// Concatenation operator.
 ///
 /// Example:
@@ -27,15 +63,19 @@ macro_rules! r {
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let fred = c!(1, 2, 3);
-/// assert_eq!(fred, Robj::from(&[1, 2, 3][..]));
+/// assert_eq!(fred, r!([1, 2, 3]));
 /// ```
+/// Note: make sure to use rust syntax for numbers: 1 is integer, 1. is numeric.
+/// For vectors of primitives, prefer to use `r!([1, 2, 3])`.
+///
+/// Panics on error.
 #[macro_export]
 macro_rules! c {
     () => {
-        lang!("c").eval_blind()
+        call!("c").unwrap()
     };
     ($($rest: tt)*) => {
-        lang!("c", $($rest)*).eval_blind()
+        call!("c", $($rest)*).unwrap()
     };
 }
 
@@ -46,12 +86,13 @@ macro_rules! c {
 /// use extendr_api::*;
 /// extendr_engine::start_r();
 /// let fred = rep!(1., 3);
-/// assert_eq!(fred, Robj::from(&[1., 1., 1.][..]));
+/// assert_eq!(fred, r!([1., 1., 1.]));
 /// ```
+/// Note: make sure to use rust syntax for numbers: 1 is integer, 1. is numeric.
 #[macro_export]
 macro_rules! rep {
     ($($rest: tt)*) => {
-        lang!("rep", $($rest)*).eval_blind()
+        call!("rep", $($rest)*).unwrap()
     };
 }
 
@@ -66,7 +107,7 @@ macro_rules! rep {
 #[macro_export]
 macro_rules! read_table {
     ($($rest: tt)*) => {
-        lang!("read.table", $($rest)*).eval()
+        call!("read.table", $($rest)*)
     };
 }
 
@@ -79,13 +120,15 @@ macro_rules! read_table {
 /// let mylist = list!(x=1, y=2);
 /// assert_eq!(mylist, List(&[1.into(), 2.into()]));
 /// ```
+///
+/// Panics on error.
 #[macro_export]
 macro_rules! list {
     () => {
-        lang!("list").eval_blind()
+        call!("list").unwrap()
     };
     ($($rest: tt)*) => {
-        lang!("list", $($rest)*).eval_blind()
+        call!("list", $($rest)*).unwrap()
     };
 }
 
@@ -98,13 +141,15 @@ macro_rules! list {
 /// let mydata = data_frame!(x=1, y=2);
 /// assert_eq!(mydata, List(&[1.into(), 2.into()]));
 /// ```
+///
+/// Panics on error.
 #[macro_export]
 macro_rules! data_frame {
     () => {
-        lang!("data.frame").eval_blind()
+        call!("data.frame").unwrap()
     };
     ($($rest: tt)*) => {
-        lang!("data.frame", $($rest)*).eval_blind()
+        call!("data.frame", $($rest)*).unwrap()
     };
 }
 
@@ -119,10 +164,12 @@ macro_rules! data_frame {
 /// 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"]);
 /// ```
+///
+/// Panics on error.
 #[macro_export]
 macro_rules! factor {
     ($($rest: tt)*) => {
-        lang!("factor", $($rest)*).eval_blind()
+        call!("factor", $($rest)*).unwrap()
     };
 }
 
diff --git a/extendr-api/src/robj.rs b/extendr-api/src/robj.rs
deleted file mode 100644
index 9f587c3de..000000000
--- a/extendr-api/src/robj.rs
+++ /dev/null
@@ -1,2420 +0,0 @@
-//! R object handling.
-//!
-//! See. https://cran.r-project.org/doc/manuals/R-exts.html
-//!
-//! Fundamental principals:
-//!
-//! * Any function that can break the protection mechanism is unsafe.
-//! * Users should be able to do almost everything without using libR_sys.
-//! * The interface should be friendly to R users without Rust experience.
-
-use libR_sys::*;
-use std::os::raw;
-
-use crate::logical::*;
-use crate::wrapper::*;
-use crate::AnyError;
-
-use std::collections::HashMap;
-use super::integer_is_na;
-use super::numeric_is_na;
-
-/// Wrapper for an R S-expression pointer (SEXP).
-///
-/// As much as possible we wish to make this object safe (ie. no segfaults).
-///
-/// If you avoid using unsafe functions it is more likely that you will avoid
-/// panics and segfaults. We will take great trouble to ensure that this
-/// is true.
-///
-pub enum Robj {
-    /// This object owns the SEXP and must free it.
-    Owned(SEXP),
-
-    /// This object references a SEXP such as an input parameter.
-    Borrowed(SEXP),
-
-    /// This object references a SEXP owned by libR.
-    Sys(SEXP),
-}
-
-pub const TRUE: bool = true;
-pub const FALSE: bool = false;
-pub const NULL: () = ();
-
-impl Clone for Robj {
-    fn clone(&self) -> Self {
-        self.duplicate()
-    }
-}
-
-impl Default for Robj {
-    fn default() -> Self {
-        Robj::from(())
-    }
-}
-
-/// Trait used for incomming parameter conversion.
-pub trait FromRobj<'a>: Sized {
-    // Convert an incomming Robj from R into a value or an error.
-    fn from_robj(_robj: &'a Robj) -> Result<Self, &'static str> {
-        Err("unable to convert value from R object")
-    }
-}
-
-macro_rules! impl_prim_from_robj {
-    ($t: ty) => {
-        impl<'a> FromRobj<'a> for $t {
-            fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-                if let Some(v) = robj.as_i32_slice() {
-                    match v.len() {
-                        0 => Err("Input must be of length 1. Vector of length zero given."),
-                        1 => if !integer_is_na(v[0]) {
-                                Ok(v[0] as Self)
-                             } else {
-                                Err("Input must not be NA.")
-                             },
-                        _ => Err("Input must be of length 1. Vector of length >1 given."),
-                    }
-                } else if let Some(v) = robj.as_f64_slice() {
-                    match v.len() {
-                        0 => Err("Input must be of length 1. Vector of length zero given."),
-                        1 => if !numeric_is_na(v[0]) {
-                            Ok(v[0] as Self)
-                         } else {
-                            Err("Input must not be NA.")
-                         },
-                        _ => Err("Input must be of length 1. Vector of length >1 given."),
-                    }
-                } else {
-                    Err("unable to convert R object to primitive")
-                }
-            }
-        }
-    };
-}
-
-impl_prim_from_robj!(u8);
-impl_prim_from_robj!(u16);
-impl_prim_from_robj!(u32);
-impl_prim_from_robj!(u64);
-impl_prim_from_robj!(i8);
-impl_prim_from_robj!(i16);
-impl_prim_from_robj!(i32);
-impl_prim_from_robj!(i64);
-impl_prim_from_robj!(f32);
-impl_prim_from_robj!(f64);
-
-impl<'a> FromRobj<'a> for bool {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if let Some(v) = robj.as_logical_slice() {
-            match v.len() {
-                0 => Err("Input must be of length 1. Vector of length zero given."),
-                1 => if !v[0].is_na() {
-                    Ok(v[0].to_bool())
-                 } else {
-                    Err("Input must not be NA.")
-                 },
-            _ => Err("Input must be of length 1. Vector of length >1 given."),
-            }
-        } else {
-            Err("Not a logical object.")
-        }
-    }
-}
-
-impl<'a> FromRobj<'a> for &'a str {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Err("Input must not be NA.")
-        } else if let Some(s) = robj.as_str() {
-            Ok(s)
-        } else {
-            Err("Not a string object.")
-        }
-    }
-}
-
-impl<'a> FromRobj<'a> for String {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Err("Input must not be NA.")
-        } else if let Some(s) = robj.as_str() {
-            Ok(s.to_string())
-        } else {
-            Err("not a string object")
-        }
-    }
-}
-
-impl<'a> FromRobj<'a> for Vec<i32> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if let Some(v) = robj.as_i32_slice() {
-            Ok(Vec::from(v))
-        } else {
-            Err("not an integer or logical vector")
-        }
-    }
-}
-
-impl<'a> FromRobj<'a> for Vec<f64> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if let Some(v) = robj.as_f64_slice() {
-            Ok(Vec::from(v))
-        } else {
-            Err("not a floating point vector")
-        }
-    }
-}
-
-macro_rules! impl_iter_from_robj {
-    ($t: ty, $iter_fn: ident, $msg: expr) => {
-        impl<'a> FromRobj<'a> for $t {
-            fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-                if let Some(v) = robj.$iter_fn() {
-                    Ok(v)
-                } else {
-                    Err($msg)
-                }
-            }
-        }
-    };
-}
-
-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!(NumericIter<'a>, numeric_iter, "Not a numeric vector.");
-impl_iter_from_robj!(LogicalIter<'a>, logical_iter, "Not a logical vector.");
-
-/// Pass-through Robj conversion.
-impl<'a> FromRobj<'a> for Robj {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        Ok(unsafe { new_borrowed(robj.get()) })
-    }
-}
-
-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() {
-            Ok(iter.map(|(k, v)| (k.to_string(), v.to_owned())).collect::<HashMap<String, Robj>>())
-        } else {
-            Err("expected a list")
-        }
-    }
-}
-
-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() {
-            Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
-        } else {
-            Err("expected a list")
-        }
-    }
-}
-
-// NA-sensitive integer input handling
-impl<'a> FromRobj<'a> for Option<i32> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Ok(None)
-        } else if let Some(val) = robj.as_integer() {
-            Ok(Some(val))
-        } else {
-            Err("expected an integer scalar")
-        }
-    }
-}
-
-// NA-sensitive logical input handling
-impl<'a> FromRobj<'a> for Option<bool> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Ok(None)
-        } else if let Some(val) = robj.as_logical() {
-            Ok(Some(val))
-        } else {
-            Err("expected a logical scalar")
-        }
-    }
-}
-
-// NA-sensitive numeric input handling
-impl<'a> FromRobj<'a> for Option<f64> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Ok(None)
-        } else if let Some(val) = robj.as_numeric() {
-            Ok(Some(val))
-        } else {
-            Err("expected a numeric scalar")
-        }
-    }
-}
-
-// NA-sensitive string input handling
-impl<'a> FromRobj<'a> for Option<&'a str> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Ok(None)
-        } else if let Some(val) = robj.as_str() {
-            Ok(Some(val))
-        } else {
-            Err("expected a character scalar")
-        }
-    }
-}
-
-// NA-sensitive string input handling
-impl<'a> FromRobj<'a> for Option<String> {
-    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
-        if robj.is_na() {
-            Ok(None)
-        } else if let Some(val) = robj.as_str() {
-            Ok(Some(val.to_string()))
-        } else {
-            Err("expected a character scalar")
-        }
-    }
-}
-
-impl Robj {
-    /// Get a copy of the underlying SEXP.
-    /// Note: this is unsafe.
-    pub unsafe fn get(&self) -> SEXP {
-        match self {
-            Robj::Owned(sexp) => *sexp,
-            Robj::Borrowed(sexp) => *sexp,
-            Robj::Sys(sexp) => *sexp,
-        }
-    }
-
-    /// Get a copy of the underlying SEXP for mutable types.
-    /// This is valid only for owned objects as we are not
-    /// permitted to modify parameters or system objects.
-    pub unsafe fn get_mut(&mut self) -> Option<SEXP> {
-        match self {
-            Robj::Owned(sexp) => Some(*sexp),
-            Robj::Borrowed(_) => None,
-            Robj::Sys(_) => None,
-        }
-    }
-
-    /// Get the XXXSXP type of the object.
-    pub fn sexptype(&self) -> u32 {
-        unsafe { TYPEOF(self.get()) as u32 }
-    }
-
-    /// Get the extended length of the object.
-    pub fn len(&self) -> usize {
-        unsafe { Rf_xlength(self.get()) as usize }
-    }
-
-    /// Is this object is an NA?
-    /// Works for character, integer and numeric types.
-    pub fn is_na(&self) -> bool {
-        if self.len() != 1 {
-            false
-        } else {
-            unsafe {
-                let sexp = self.get();
-                match self.sexptype() {
-                    STRSXP => STRING_ELT(sexp, 0) == libR_sys::R_NaString,
-                    INTSXP => *(INTEGER(sexp)) == libR_sys::R_NaInt,
-                    LGLSXP => *(LOGICAL(sexp)) == libR_sys::R_NaInt,
-                    REALSXP => R_IsNA(*(REAL(sexp))) != 0,
-                    _ => false,
-                }
-            }
-        }
-    }
-
-    /// Get a read-only reference to the content of an integer or logical vector.
-    pub fn as_i32_slice(&self) -> Option<&[i32]> {
-        self.as_typed_slice()
-    }
-
-    /// Get a read-only reference to the content of an integer vector.
-    pub fn as_integer_slice(&self) -> Option<&[i32]> {
-        self.as_typed_slice()
-    }
-
-    /// Get an iterator over integer elements of this slice.
-    pub fn integer_iter(&self) -> Option<IntegerIter> {
-        if let Some(slice) = self.as_integer_slice() {
-            Some(slice.iter())
-        } else {
-            None
-        }
-    }
-
-    /// Get a Vec<i32> copied from the object.
-    pub fn as_integer_vector(&self) -> Option<Vec<i32>> {
-        if let Some(value) = self.as_integer_slice() {
-            Some(value.iter().cloned().collect::<Vec<_>>())
-        } else {
-            None
-        }
-    }
-
-    /// Get a read-only reference to the content of an integer or logical vector.
-    pub fn as_logical_slice(&self) -> Option<&[Bool]> {
-        self.as_typed_slice()
-    }
-
-    /// Get a Vec<bool> copied from the object.
-    pub fn as_logical_vector(&self) -> Option<Vec<bool>> {
-        if let Some(value) = self.as_logical_slice() {
-            Some(value.iter().map(|x| x.clone().into()).collect::<Vec<_>>())
-        } else {
-            None
-        }
-    }
-
-    /// Get an iterator over logical elements of this slice.
-    pub fn logical_iter(&self) -> Option<LogicalIter> {
-        if let Some(slice) = self.as_logical_slice() {
-            Some(slice.iter())
-        } else {
-            None
-        }
-    }
-
-    /// Get a read-only reference to the content of a double vector.
-    pub fn as_f64_slice(&self) -> Option<&[f64]> {
-        self.as_typed_slice()
-    }
-
-    /// Get a read-only reference to the content of a double vector.
-    pub fn as_numeric_slice(&self) -> Option<&[f64]> {
-        self.as_typed_slice()
-    }
-
-    /// Get an iterator over numeric elements of this slice.
-    pub fn numeric_iter(&self) -> Option<NumericIter> {
-        if let Some(slice) = self.as_numeric_slice() {
-            Some(slice.iter())
-        } else {
-            None
-        }
-    }
-
-    /// Get a Vec<f64> copied from the object.
-    pub fn as_numeric_vector(&self) -> Option<Vec<f64>> {
-        if let Some(value) = self.as_numeric_slice() {
-            Some(value.iter().cloned().collect::<Vec<_>>())
-        } else {
-            None
-        }
-    }
-
-    /// Get a read-only reference to the content of an integer or logical vector.
-    pub fn as_u8_slice(&self) -> Option<&[u8]> {
-        self.as_typed_slice()
-    }
-
-    /// Get a read-only reference to the content of an integer or logical vector.
-    pub fn as_raw_slice(&self) -> Option<&[u8]> {
-        self.as_typed_slice()
-    }
-
-    /// Get a read-write reference to the content of an integer or logical vector.
-    pub fn as_i32_slice_mut(&mut self) -> Option<&mut [i32]> {
-        self.as_typed_slice_mut()
-    }
-
-    /// Get a read-write reference to the content of a double vector.
-    pub fn as_f64_slice_mut(&mut self) -> Option<&mut [f64]> {
-        self.as_typed_slice_mut()
-    }
-
-    /// Get a read-write reference to the content of an integer or logical vector.
-    pub fn as_u8_slice_mut(&mut self) -> Option<&mut [u8]> {
-        self.as_typed_slice_mut()
-    }
-
-    /// Get an iterator over a pairlist.
-    pub fn pairlist_iter(&self) -> Option<ListIter> {
-        match self.sexptype() {
-            LISTSXP | LANGSXP | DOTSXP => unsafe {
-                Some(ListIter {
-                    list_elem: self.get(),
-                })
-            },
-            _ => None,
-        }
-    }
-
-    /// Get an iterator over an unnamed list.
-    pub fn list_iter(&self) -> Option<VecIter> {
-        match self.sexptype() {
-            VECSXP | EXPRSXP | WEAKREFSXP => unsafe {
-                Some(VecIter {
-                    vector: self.get(),
-                    i: 0,
-                    len: self.len(),
-                })
-            },
-            _ => None,
-        }
-    }
-
-    /// Get an iterator over a string vector.
-    /// Returns None if the object is not a string vector
-    /// but works for factors.
-    ///
-    /// ```
-    /// use extendr_api::*;
-    ///
-    /// extendr_engine::start_r();
-    ///
-    /// let obj = Robj::from(vec!["a", "b", "c"]);
-    /// assert_eq!(obj.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"]);
-    /// ```
-    pub fn str_iter(&self) -> Option<StrIter> {
-        let i = 0;
-        let len = self.len();
-        match self.sexptype() {
-            STRSXP => unsafe {
-                let vector = self.get();
-                Some(StrIter {vector, i, len, levels: R_NilValue})
-            },
-            INTSXP => unsafe {
-                let vector = self.get();
-                let levels = self.getAttrib(&Robj::levelsSymbol());
-                if self.isFactor() && levels.sexptype() == STRSXP {
-                    Some(StrIter {vector, i, len, levels: levels.get()})
-                } else {
-                    None
-                }
-            },
-            _ => None,
-        }
-    }
-
-    /// Get a vector of owned strings.
-    /// Owned strings have long lifetimes, but are much slower than references.
-    /// ```
-    ///    use extendr_api::*;
-    ///    extendr_engine::start_r();
-    ///    let robj1 = Robj::from("xyz");
-    ///    assert_eq!(robj1.as_string_vector(), Some(vec!["xyz".to_string()]));
-    ///    let robj2 = Robj::from(1);
-    ///    assert_eq!(robj2.as_string_vector(), None);
-    /// ```
-    pub fn as_string_vector(&self) -> Option<Vec<String>> {
-        if let Some(iter) = self.str_iter() {
-            Some(iter.map(str::to_string).collect())
-        } else {
-            None
-        }
-    }
-
-    /// Get a vector of string references.
-    /// String references (&str) are faster, but have short lifetimes.
-    /// ```
-    ///    use extendr_api::*;
-    ///    extendr_engine::start_r();
-    ///    let robj1 = Robj::from("xyz");
-    ///    assert_eq!(robj1.as_str_vector(), Some(vec!["xyz"]));
-    ///    let robj2 = Robj::from(1);
-    ///    assert_eq!(robj2.as_str_vector(), None);
-    /// ```
-    pub fn as_str_vector(&self) -> Option<Vec<&str>> {
-        if let Some(iter) = self.str_iter() {
-            Some(iter.collect())
-        } else {
-            None
-        }
-    }
-
-    /// Get a read-only reference to a char, symbol or string type.
-    pub fn as_str(&self) -> Option<&str> {
-        unsafe {
-            match self.sexptype() {
-                STRSXP => {
-                    if self.len() != 1 {
-                        None
-                    } else {
-                        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)),
-                _ => None,
-            }
-        }
-    }
-
-    /// Get a scalar integer.
-    pub fn as_i32(&self) -> Option<i32> {
-        match self.as_i32_slice() {
-            Some(slice) if slice.len() == 1 => Some(slice[0]),
-            _ => None
-        }
-    }
-
-    /// Get a scalar integer.
-    pub fn as_integer(&self) -> Option<i32> {
-        self.as_i32()
-    }
-
-    /// Get a scalar real.
-    pub fn as_f64(&self) -> Option<f64> {
-        match self.as_f64_slice() {
-            Some(slice) if slice.len() == 1 => Some(slice[0]),
-            _ => None
-        }
-    }
-
-    /// Get a scalar real.
-    pub fn as_numeric(&self) -> Option<f64> {
-        self.as_f64()
-    }
-
-    /// Get a scalar boolean.
-    pub fn as_bool(&self) -> Option<bool> {
-        match self.as_logical_slice() {
-            Some(slice) if slice.len() == 1 => Some(slice[0].into()),
-            _ => None
-        }
-    }
-
-    /// Get a scalar boolean.
-    pub fn as_logical(&self) -> Option<bool> {
-        match self.as_logical_slice() {
-            Some(slice) if slice.len() == 1 => Some(slice[0].into()),
-            _ => None
-        }
-    }
-
-    /// Evaluate the expression and return an error or an R object.
-    pub fn eval(&self) -> Result<Robj, AnyError> {
-        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 {
-                Err(AnyError::from("R eval error"))
-            } else {
-                Ok(Robj::from(res))
-            }
-        }
-    }
-
-    /// Evaluate the expression and return NULL or an R object.
-    pub fn eval_blind(&self) -> Robj {
-        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 {
-                Robj::from(())
-            } else {
-                Robj::from(res)
-            }
-        }
-    }
-
-    /// Parse a string into an R executable object
-    pub fn parse(code: &str) -> Result<Robj, AnyError> {
-        unsafe {
-            use libR_sys::*;
-            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));
-            match status {
-                1 => Ok(parsed),
-                _ => Err(AnyError::from("parse_error")),
-            }
-        }
-    }
-
-    /// Parse a string into an R executable object and run it.
-    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()?;
-            }
-        }
-        Ok(res)
-    }
-
-    /// Unprotect an object - assumes a transfer of ownership.
-    /// This is unsafe because the object pointer may be left dangling.
-    pub unsafe fn unprotected(self) -> Robj {
-        match self {
-            Robj::Owned(sexp) => {
-                R_ReleaseObject(sexp);
-                Robj::Borrowed(sexp)
-            }
-            _ => self,
-        }
-    }
-
-    /// Return true if the object is owned by this wrapper.
-    /// If so, it will be released when the wrapper drops.
-    pub fn is_owned(&self) -> bool {
-        match self {
-            Robj::Owned(_) => true,
-            _ => false,
-        }
-    }
-
-    // Convert the Robj to an owned one.
-    pub fn to_owned(self) -> Robj {
-        match self {
-            Robj::Owned(_) => self,
-            _ => unsafe { new_owned(self.get()) }
-        }
-    }
-}
-
-pub trait AsTypedSlice<T> {
-    fn as_typed_slice(&self) -> Option<&[T]> {
-        None
-    }
-    fn as_typed_slice_mut(&mut self) -> Option<&mut [T]> {
-        None
-    }
-}
-
-macro_rules! make_typed_slice {
-    ($type: ty, $fn: tt, $($sexp: tt),* ) => {
-        impl AsTypedSlice<$type> for Robj {
-            fn as_typed_slice(&self) -> Option<&[$type]> {
-                match self.sexptype() {
-                    $( $sexp )|* => {
-                        unsafe {
-                            let ptr = $fn(self.get()) as *const $type;
-                            Some(std::slice::from_raw_parts(ptr, self.len()))
-                        }
-                    }
-                    _ => None
-                }
-            }
-
-            fn as_typed_slice_mut(&mut self) -> Option<&mut [$type]> {
-                match self.sexptype() {
-                    $( $sexp )|* => {
-                        unsafe {
-                            let ptr = $fn(self.get()) as *mut $type;
-                            Some(std::slice::from_raw_parts_mut(ptr, self.len()))
-                        }
-                    }
-                    _ => None
-                }
-            }
-        }
-    }
-}
-
-make_typed_slice!(Bool, INTEGER, LGLSXP);
-make_typed_slice!(i32, INTEGER, INTSXP);
-make_typed_slice!(f64, REAL, REALSXP);
-make_typed_slice!(u8, RAW, RAWSXP);
-
-// These are helper functions which give access to common properties of R objects.
-#[allow(non_snake_case)]
-impl Robj {
-    /// Get the names attribute as a string iterator if one exists.
-    pub fn names(&self) -> Option<StrIter> {
-        self.getAttrib(&Robj::namesSymbol()).str_iter()
-    }
-
-    /// Return an iterator over names and values of a list if they exist.
-    pub fn named_list_iter(&self) -> Option<NamedListIter> {
-        if let Some(names) = self.names() {
-            if let Some(values) = self.list_iter() {
-                return Some(names.zip(values));
-            }
-        }
-        None
-    }
-
-    /// Get the class attribute as a string iterator if one exists.
-    pub fn class(&self) -> Option<StrIter> {
-        self.getAttrib(&Robj::classSymbol()).str_iter()
-    }
-
-    /// Return true if this class inherits this class.
-    pub fn inherits(&self, classname: &str) -> bool {
-        if let Some(mut iter) = self.class() {
-            iter.find(|&n| n == classname).is_some()
-        } else {
-            false
-        }
-    }
-
-    /// Get the levels attribute as a string iterator if one exists.
-    pub fn levels(&self) -> Option<StrIter> {
-        self.getAttrib(&Robj::levelsSymbol()).str_iter()
-    }
-}
-
-///////////////////////////////////////////////////////////////
-/// The following impls wrap specific Rinternals.h symbols.
-///
-#[allow(non_snake_case)]
-impl Robj {
-    /// The "global" environment
-    pub fn globalEnv() -> Robj {
-        unsafe { new_sys(R_GlobalEnv) }
-    }
-    /// An empty environment at the root of the environment tree
-    pub fn emptyEnv() -> Robj {
-        unsafe { new_sys(R_EmptyEnv) }
-    }
-    /// The base environment; formerly R_NilValue
-    pub fn baseEnv() -> Robj {
-        unsafe { new_sys(R_BaseEnv) }
-    }
-    /// The (fake) namespace for base
-    pub fn baseNamespace() -> Robj {
-        unsafe { new_sys(R_BaseNamespace) }
-    }
-    /// for registered namespaces
-    pub fn namespaceRegistry() -> Robj {
-        unsafe { new_sys(R_NamespaceRegistry) }
-    }
-    /// Current srcref, for debuggers
-    pub fn srcref() -> Robj {
-        unsafe { new_sys(R_Srcref) }
-    }
-    /// The nil object
-    pub fn nilValue() -> Robj {
-        unsafe { new_sys(R_NilValue) }
-    }
-    /// Unbound marker
-    pub fn unboundValue() -> Robj {
-        unsafe { new_sys(R_UnboundValue) }
-    }
-    /// Missing argument marker
-    pub fn missingArg() -> Robj {
-        unsafe { new_sys(R_MissingArg) }
-    }
-
-    /* Not supported by older R versions.
-    /// To be found in BC interp. state (marker)
-    pub fn inBCInterpreter() -> Robj { unsafe { new_sys(R_InBCInterpreter) }}
-    /// Use current expression (marker)
-    pub fn currentExpression() -> Robj { unsafe { new_sys(R_CurrentExpression) }}
-    /// character"
-    pub fn asCharacterSymbol() -> Robj { unsafe { new_sys(R_AsCharacterSymbol) }}
-    */
-
-    /// "base"
-    pub fn baseSymbol() -> Robj {
-        unsafe { new_sys(R_BaseSymbol) }
-    }
-    /// "{"
-    pub fn braceSymbol() -> Robj {
-        unsafe { new_sys(R_BraceSymbol) }
-    }
-    /// "[["
-    pub fn bracket2Symbol() -> Robj {
-        unsafe { new_sys(R_Bracket2Symbol) }
-    }
-    /// "["
-    pub fn bracketSymbol() -> Robj {
-        unsafe { new_sys(R_BracketSymbol) }
-    }
-    /// "class"
-    pub fn classSymbol() -> Robj {
-        unsafe { new_sys(R_ClassSymbol) }
-    }
-    /// ".Device"
-    pub fn deviceSymbol() -> Robj {
-        unsafe { new_sys(R_DeviceSymbol) }
-    }
-    /// "dimnames"
-    pub fn dimNamesSymbol() -> Robj {
-        unsafe { new_sys(R_DimNamesSymbol) }
-    }
-    /// "dim"
-    pub fn dimSymbol() -> Robj {
-        unsafe { new_sys(R_DimSymbol) }
-    }
-    /// "$"
-    pub fn dollarSymbol() -> Robj {
-        unsafe { new_sys(R_DollarSymbol) }
-    }
-    /// "..."
-    pub fn dotsSymbol() -> Robj {
-        unsafe { new_sys(R_DotsSymbol) }
-    }
-    ///     pub fn dropSymbol() -> Robj { unsafe { new_sys(R_DropSymbol) }}"drop"
-    pub fn doubleColonSymbol() -> Robj {
-        unsafe { new_sys(R_DoubleColonSymbol) }
-    } //
-    /// ".Last.value"
-    pub fn lastvalueSymbol() -> Robj {
-        unsafe { new_sys(R_LastvalueSymbol) }
-    }
-    /// "levels"
-    pub fn levelsSymbol() -> Robj {
-        unsafe { new_sys(R_LevelsSymbol) }
-    }
-    /// "mode"
-    pub fn modeSymbol() -> Robj {
-        unsafe { new_sys(R_ModeSymbol) }
-    }
-    /// "na.rm"
-    pub fn naRmSymbol() -> Robj {
-        unsafe { new_sys(R_NaRmSymbol) }
-    }
-    /// "name"
-    pub fn nameSymbol() -> Robj {
-        unsafe { new_sys(R_NameSymbol) }
-    }
-    /// "names"
-    pub fn namesSymbol() -> Robj {
-        unsafe { new_sys(R_NamesSymbol) }
-    }
-    /// _NAMESPACE__."
-    pub fn namespaceEnvSymbol() -> Robj {
-        unsafe { new_sys(R_NamespaceEnvSymbol) }
-    }
-    /// "package"
-    pub fn packageSymbol() -> Robj {
-        unsafe { new_sys(R_PackageSymbol) }
-    }
-    /// "previous"
-    pub fn previousSymbol() -> Robj {
-        unsafe { new_sys(R_PreviousSymbol) }
-    }
-    /// "quote"
-    pub fn quoteSymbol() -> Robj {
-        unsafe { new_sys(R_QuoteSymbol) }
-    }
-    /// "row.names"
-    pub fn rowNamesSymbol() -> Robj {
-        unsafe { new_sys(R_RowNamesSymbol) }
-    }
-    /// ".Random.seed"
-    pub fn seedsSymbol() -> Robj {
-        unsafe { new_sys(R_SeedsSymbol) }
-    }
-    /// "sort.list"
-    pub fn sortListSymbol() -> Robj {
-        unsafe { new_sys(R_SortListSymbol) }
-    }
-    /// "source"
-    pub fn sourceSymbol() -> Robj {
-        unsafe { new_sys(R_SourceSymbol) }
-    }
-    /// "spec"
-    pub fn specSymbol() -> Robj {
-        unsafe { new_sys(R_SpecSymbol) }
-    }
-    /// "tsp"
-    pub fn tspSymbol() -> Robj {
-        unsafe { new_sys(R_TspSymbol) }
-    }
-    /// ":::"
-    pub fn tripleColonSymbol() -> Robj {
-        unsafe { new_sys(R_TripleColonSymbol) }
-    }
-    /// ".defined"
-    pub fn dot_defined() -> Robj {
-        unsafe { new_sys(R_dot_defined) }
-    }
-    /// ".Method"
-    pub fn dot_Method() -> Robj {
-        unsafe { new_sys(R_dot_Method) }
-    }
-    /// "packageName"
-    pub fn dot_packageName() -> Robj {
-        unsafe { new_sys(R_dot_packageName) }
-    } //
-    /// ".target"
-    pub fn dot_target() -> Robj {
-        unsafe { new_sys(R_dot_target) }
-    }
-    /* fix version issues.
-    /// ".Generic"
-    pub fn dot_Generic() -> Robj { unsafe { new_sys(R_dot_Generic) }}
-    */
-    /// NA_STRING as a CHARSXP
-    pub fn naString() -> Robj {
-        unsafe { new_sys(R_NaString) }
-    }
-    /// "" as a CHARSXP
-    pub fn blankString() -> Robj {
-        unsafe { new_sys(R_BlankString) }
-    }
-    /// as a STRSXP
-    pub fn blankScalarString() -> Robj {
-        unsafe { new_sys(R_BlankScalarString) }
-    }
-}
-
-///////////////////////////////////////////////////////////////
-/// The following impls wrap specific Rinternals.h functions.
-///
-#[allow(non_snake_case)]
-impl Robj {
-    /// Return true if this is the null object.
-    pub fn isNull(&self) -> bool {
-        unsafe { Rf_isNull(self.get()) != 0 }
-    }
-
-    /// Return true if this is a symbol.
-    pub fn isSymbol(&self) -> bool {
-        unsafe { Rf_isSymbol(self.get()) != 0 }
-    }
-
-    /// Return true if this is a boolean (logical) vector
-    pub fn isLogical(&self) -> bool {
-        unsafe { Rf_isLogical(self.get()) != 0 }
-    }
-
-    /// Return true if this is a real (f64) vector.
-    pub fn isReal(&self) -> bool {
-        unsafe { Rf_isReal(self.get()) != 0 }
-    }
-
-    /// Return true if this is a complex vector.
-    pub fn isComplex(&self) -> bool {
-        unsafe { Rf_isComplex(self.get()) != 0 }
-    }
-
-    /// Return true if this is an expression.
-    pub fn isExpression(&self) -> bool {
-        unsafe { Rf_isExpression(self.get()) != 0 }
-    }
-
-    /// Return true if this is an environment.
-    pub fn isEnvironment(&self) -> bool {
-        unsafe { Rf_isEnvironment(self.get()) != 0 }
-    }
-
-    /// Return true if this is a string.
-    pub fn isString(&self) -> bool {
-        unsafe { Rf_isString(self.get()) != 0 }
-    }
-
-    /// Return true if this is an object.
-    pub fn isObject(&self) -> bool {
-        unsafe { Rf_isObject(self.get()) != 0 }
-    }
-
-    /// Get the source ref.
-    pub fn getCurrentSrcref(val: i32) -> Robj {
-        unsafe { new_owned(R_GetCurrentSrcref(val as raw::c_int)) }
-    }
-
-    /// Get the source filename.
-    pub fn getSrcFilename(&self) -> Robj {
-        unsafe { new_owned(R_GetSrcFilename(self.get())) }
-    }
-
-    /// Convert to a string vector.
-    pub fn asChar(&self) -> Robj {
-        unsafe { new_owned(Rf_asChar(self.get())) }
-    }
-
-    /// Convert to vectors of many kinds.
-    pub fn coerceVector(&self, sexptype: u32) -> Robj {
-        unsafe { new_owned(Rf_coerceVector(self.get(), sexptype as SEXPTYPE)) }
-    }
-
-    /// Convert a pairlist (LISTSXP) to a vector list (VECSXP).
-    pub fn pairToVectorList(&self) -> Robj {
-        unsafe { new_owned(Rf_PairToVectorList(self.get())) }
-    }
-
-    /// Convert a vector list (VECSXP) to a pair list (LISTSXP)
-    pub fn vectorToPairList(&self) -> Robj {
-        unsafe { new_owned(Rf_VectorToPairList(self.get())) }
-    }
-
-    /// Convert a factor to a string vector.
-    pub fn asCharacterFactor(&self) -> Robj {
-        unsafe { new_owned(Rf_asCharacterFactor(self.get())) }
-    }
-
-    /// Get a scalar boolean value
-    pub fn asLogical(&self) -> bool {
-        unsafe { Rf_asLogical(self.get()) != 0 }
-    }
-
-    /// Get a scalar 32 bit integer value
-    pub fn asInteger(&self) -> i32 {
-        unsafe { Rf_asInteger(self.get()) as i32 }
-    }
-
-    /// Get a 64 bit double value
-    pub fn asReal(&self) -> f64 {
-        unsafe { Rf_asReal(self.get()) as f64 }
-    }
-
-    /// Allocate a matrix object (see NumericMatrix etc.)
-    pub fn allocMatrix(sexptype: SEXPTYPE, rows: i32, cols: i32) -> Robj {
-        unsafe { new_owned(Rf_allocMatrix(sexptype, rows, cols)) }
-    }
-
-    /* TODO:
-    int Rf_asLogical2(SEXP x, int checking, SEXP call, SEXP rho);
-    Rcomplex Rf_asComplex(SEXP x);
-    void Rf_addMissingVarsToNewEnv(SEXP, SEXP);
-    SEXP Rf_alloc3DArray(SEXPTYPE, int, int, int);
-    SEXP Rf_allocArray(SEXPTYPE, SEXP);
-    SEXP Rf_allocFormalsList2(SEXP sym1, SEXP sym2);
-    SEXP Rf_allocFormalsList3(SEXP sym1, SEXP sym2, SEXP sym3);
-    SEXP Rf_allocFormalsList4(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4);
-    SEXP Rf_allocFormalsList5(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4, SEXP sym5);
-    SEXP Rf_allocFormalsList6(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4, SEXP sym5, SEXP sym6);
-    SEXP Rf_allocList(int);
-    SEXP Rf_allocS4Object(void);
-    SEXP Rf_allocSExp(SEXPTYPE);
-    SEXP Rf_allocVector3(SEXPTYPE, R_xlen_t, R_allocator_t*);
-    R_xlen_t Rf_any_duplicated(SEXP x, Rboolean from_last);
-    R_xlen_t Rf_any_duplicated3(SEXP x, SEXP incomp, Rboolean from_last);
-    SEXP Rf_applyClosure(SEXP, SEXP, SEXP, SEXP, SEXP);
-    SEXP Rf_arraySubscript(int, SEXP, SEXP, SEXP (*)(SEXP,SEXP), SEXP (*)(SEXP, int), SEXP);
-    SEXP Rf_classgets(SEXP, SEXP);
-    SEXP Rf_cons(SEXP, SEXP);
-    SEXP Rf_fixSubset3Args(SEXP, SEXP, SEXP, SEXP*);
-    void Rf_copyMatrix(SEXP, SEXP, Rboolean);
-    void Rf_copyListMatrix(SEXP, SEXP, Rboolean);
-    void Rf_copyMostAttrib(SEXP, SEXP);
-    void Rf_copyVector(SEXP, SEXP);
-    int Rf_countContexts(int, int);
-    SEXP Rf_CreateTag(SEXP);
-    void Rf_defineVar(SEXP, SEXP, SEXP);
-    SEXP Rf_dimgets(SEXP, SEXP);
-    SEXP Rf_dimnamesgets(SEXP, SEXP);
-    SEXP Rf_DropDims(SEXP);
-    */
-
-    /// 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())) }
-    }
-
-    /*
-    SEXP Rf_shallow_duplicate(SEXP);
-    SEXP R_duplicate_attr(SEXP);
-    SEXP R_shallow_duplicate_attr(SEXP);
-    SEXP Rf_lazy_duplicate(SEXP);
-    SEXP Rf_duplicated(SEXP, Rboolean);
-    Rboolean R_envHasNoSpecialSymbols(SEXP);
-    SEXP Rf_eval(SEXP, SEXP);
-    SEXP Rf_ExtractSubset(SEXP, SEXP, SEXP);
-    SEXP Rf_findFun(SEXP, SEXP);
-    SEXP Rf_findFun3(SEXP, SEXP, SEXP);
-    void Rf_findFunctionForBody(SEXP);
-    SEXP Rf_findVar(SEXP, SEXP);
-    SEXP Rf_findVarInFrame(SEXP, SEXP);
-    SEXP Rf_findVarInFrame3(SEXP, SEXP, Rboolean);
-    */
-
-    /// Get a specific attribute as a borrowed robj.
-    /// Return R_NilValue on error.
-    pub fn getAttrib(&self, name: &Robj) -> Robj {
-        if self.sexptype() == CHARSXP {
-            // Avoid R error.
-            Robj::from(NULL)
-        } else {
-            unsafe { new_borrowed(Rf_getAttrib(self.get(), name.get())) }
-        }
-    }
-
-    /*
-    SEXP Rf_GetArrayDimnames(SEXP);
-    SEXP Rf_GetColNames(SEXP);
-    void Rf_GetMatrixDimnames(SEXP, SEXP*, SEXP*, const char**, const char**);
-    SEXP Rf_GetOption(SEXP, SEXP);
-    SEXP Rf_GetOption1(SEXP);
-    int Rf_FixupDigits(SEXP, warn_type);
-    int Rf_FixupWidth (SEXP, warn_type);
-    int Rf_GetOptionDigits(void);
-    int Rf_GetOptionWidth(void);
-    SEXP Rf_GetRowNames(SEXP);
-    void Rf_gsetVar(SEXP, SEXP, SEXP);
-    SEXP Rf_install(const char *);
-    SEXP Rf_installChar(SEXP);
-    SEXP Rf_installNoTrChar(SEXP);
-    SEXP Rf_installTrChar(SEXP);
-    SEXP Rf_installDDVAL(int i);
-    SEXP Rf_installS3Signature(const char *, const char *);
-    Rboolean Rf_isFree(SEXP);
-    Rboolean Rf_isOrdered(SEXP);
-    Rboolean Rf_isUnmodifiedSpecSym(SEXP sym, SEXP env);
-    Rboolean Rf_isUnordered(SEXP);
-    Rboolean Rf_isUnsorted(SEXP, Rboolean);
-    SEXP Rf_lengthgets(SEXP, R_len_t);
-    SEXP Rf_xlengthgets(SEXP, R_xlen_t);
-    SEXP R_lsInternal(SEXP, Rboolean);
-    SEXP R_lsInternal3(SEXP, Rboolean, Rboolean);
-    SEXP Rf_match(SEXP, SEXP, int);
-    SEXP Rf_matchE(SEXP, SEXP, int, SEXP);
-    SEXP Rf_namesgets(SEXP, SEXP);
-    SEXP Rf_mkChar(const char *);
-    SEXP Rf_mkCharLen(const char *, int);
-    Rboolean Rf_NonNullStringMatch(SEXP, SEXP);
-    */
-
-    /// Number of columns of a matrix
-    pub fn ncols(&self) -> usize {
-        unsafe { Rf_ncols(self.get()) as usize }
-    }
-
-    /// Number of rows of a matrix
-    pub fn nrows(&self) -> usize {
-        unsafe { Rf_nrows(self.get()) as usize }
-    }
-
-    /*SEXP Rf_nthcdr(SEXP, int);
-    Rboolean Rf_pmatch(SEXP, SEXP, Rboolean);
-    Rboolean Rf_psmatch(const char *, const char *, Rboolean);
-    void Rf_PrintValue(SEXP);
-    void Rf_printwhere(void);
-    void Rf_readS3VarsFromFrame(SEXP, SEXP*, SEXP*, SEXP*, SEXP*, SEXP*, SEXP*);
-    SEXP Rf_setAttrib(SEXP, SEXP, SEXP);
-    void Rf_setSVector(SEXP*, int, SEXP);
-    void Rf_setVar(SEXP, SEXP, SEXP);
-    SEXP Rf_stringSuffix(SEXP, int);
-    SEXPTYPE Rf_str2type(const char *);
-    Rboolean Rf_StringBlank(SEXP);
-    SEXP Rf_substitute(SEXP,SEXP);
-    SEXP Rf_topenv(SEXP, SEXP);
-    const char * Rf_translateChar(SEXP);
-    const char * Rf_translateChar0(SEXP);
-    const char * Rf_translateCharUTF8(SEXP);
-    const char * Rf_type2char(SEXPTYPE);
-    SEXP Rf_type2rstr(SEXPTYPE);
-    SEXP Rf_type2str(SEXPTYPE);
-    SEXP Rf_type2str_nowarn(SEXPTYPE);
-    SEXP R_GetCurrentEnv();
-    Rboolean Rf_isS4(SEXP);
-    SEXP Rf_asS4(SEXP, Rboolean, int);
-    SEXP Rf_S3Class(SEXP);
-    int Rf_isBasicClass(const char *);
-    Rboolean R_cycle_detected(SEXP s, SEXP child);
-    u32 Rf_getCharCE(SEXP);
-    SEXP Rf_mkCharCE(const char *, cetype_t);
-    SEXP Rf_mkCharLenCE(const char *, int, cetype_t);
-    SEXP R_forceAndCall(SEXP e, int n, SEXP rho);
-    */
-
-    /// Internal function used to implement #[extendr] impl
-    pub unsafe fn makeExternalPtr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
-        new_owned(R_MakeExternalPtr(
-            p as *mut ::std::os::raw::c_void,
-            tag.get(),
-            prot.get(),
-        ))
-    }
-
-    /// Internal function used to implement #[extendr] impl
-    pub unsafe fn externalPtrAddr<T>(&self) -> *mut T {
-        R_ExternalPtrAddr(self.get()) as *mut T
-    }
-
-    /// Internal function used to implement #[extendr] impl
-    pub unsafe fn externalPtrTag(&self) -> Self {
-        new_borrowed(R_ExternalPtrTag(self.get()))
-    }
-
-    /// Internal function used to implement #[extendr] impl
-    pub unsafe fn externalPtrProtected(&self) -> Self {
-        new_borrowed(R_ExternalPtrProtected(self.get()))
-    }
-
-    pub unsafe fn registerCFinalizer(&self, func: R_CFinalizer_t) {
-        R_RegisterCFinalizer(self.get(), func);
-    }
-
-    // SEXP R_ExternalPtrTag(SEXP s);
-    // SEXP R_ExternalPtrProtected(SEXP s);
-    // void R_ClearExternalPtr(SEXP s);
-    // void R_SetExternalPtrAddr(SEXP s, void *p);
-    // void R_SetExternalPtrTag(SEXP s, SEXP tag);
-    // void R_SetExternalPtrProtected(SEXP s, SEXP p);
-
-    /*
-    SEXP R_MakeWeakRef(SEXP key, SEXP val, SEXP fin, Rboolean onexit);
-    SEXP R_MakeWeakRefC(SEXP key, SEXP val, R_CFinalizer_t fin, Rboolean onexit);
-    SEXP R_WeakRefKey(SEXP w);
-    SEXP R_WeakRefValue(SEXP w);
-    void R_RunWeakRefFinalizer(SEXP w);
-    SEXP R_PromiseExpr(SEXP);
-    SEXP R_ClosureExpr(SEXP);
-    SEXP R_BytecodeExpr(SEXP e);
-    SEXP R_bcEncode(SEXP);
-    SEXP R_bcDecode(SEXP);
-    void R_registerBC(SEXP, SEXP);
-    Rboolean R_checkConstants(Rboolean);
-    Rboolean R_BCVersionOK(SEXP);
-    void R_RestoreHashCount(SEXP rho);
-    Rboolean R_IsPackageEnv(SEXP rho);
-    SEXP R_PackageEnvName(SEXP rho);
-    SEXP R_FindPackageEnv(SEXP info);
-    Rboolean R_IsNamespaceEnv(SEXP rho);
-    SEXP R_NamespaceEnvSpec(SEXP rho);
-    SEXP R_FindNamespace(SEXP info);
-    void R_LockEnvironment(SEXP env, Rboolean bindings);
-    Rboolean R_EnvironmentIsLocked(SEXP env);
-    void R_LockBinding(SEXP sym, SEXP env);
-    void R_unLockBinding(SEXP sym, SEXP env);
-    void R_MakeActiveBinding(SEXP sym, SEXP fun, SEXP env);
-    Rboolean R_BindingIsLocked(SEXP sym, SEXP env);
-    Rboolean R_BindingIsActive(SEXP sym, SEXP env);
-    Rboolean R_HasFancyBindings(SEXP rho);
-    */
-
-    /// Read-only access to attribute list.
-    // fn attrib(&self) -> Robj {
-    // unsafe {new_borrowed(ATTRIB(self.get()))}
-    // }
-
-    /// Copy a vector and resize it.
-    /// See. https://github.com/hadley/r-internals/blob/master/vectors.md
-    pub fn xlengthgets(&self, new_len: usize) -> Result<Robj, AnyError> {
-        unsafe {
-            if self.isVector() {
-                Ok(new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t)))
-            } else {
-                Err(AnyError::from("xlengthgets: Not a vector type"))
-            }
-        }
-    }
-
-    /// Allocated an owned object of a certain type.
-    pub fn allocVector(sexptype: u32, len: usize) -> Robj {
-        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 }
-    }
-
-    /// 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)) }
-    }
-
-    //Rboolean Rf_inherits(SEXP, const char *);
-
-    /// Return true if this is an array.
-    pub fn isArray(&self) -> bool {
-        unsafe { Rf_isArray(self.get()) != 0 }
-    }
-
-    /// Return true if this is factor.
-    pub fn isFactor(&self) -> bool {
-        unsafe { Rf_isFactor(self.get()) != 0 }
-    }
-
-    /// Return true if this is a data frame.
-    pub fn isFrame(&self) -> bool {
-        unsafe { Rf_isFrame(self.get()) != 0 }
-    }
-
-    /// Return true if this is a function.
-    pub fn isFunction(&self) -> bool {
-        unsafe { Rf_isFunction(self.get()) != 0 }
-    }
-
-    /// Return true if this is an integer vector.
-    pub fn isInteger(&self) -> bool {
-        unsafe { Rf_isInteger(self.get()) != 0 }
-    }
-
-    /// Return true if this is a language object.
-    pub fn isLanguage(&self) -> bool {
-        unsafe { Rf_isLanguage(self.get()) != 0 }
-    }
-
-    /// Return true if this is a vector list.
-    pub fn isList(&self) -> bool {
-        unsafe { Rf_isList(self.get()) != 0 }
-    }
-
-    /// Return true if this is a matrix.
-    pub fn isMatrix(&self) -> bool {
-        unsafe { Rf_isMatrix(self.get()) != 0 }
-    }
-
-    /// Return true if this is a vector list or null.
-    pub fn isNewList(&self) -> bool {
-        unsafe { Rf_isNewList(self.get()) != 0 }
-    }
-
-    /// Return true if this is a numeric vector but not a factor.
-    pub fn isNumber(&self) -> bool {
-        unsafe { Rf_isNumber(self.get()) != 0 }
-    }
-
-    /// Return true if this is a numeric vector but not a factor or complex.
-    pub fn isNumeric(&self) -> bool {
-        unsafe { Rf_isNumeric(self.get()) != 0 }
-    }
-
-    /// Return true if this is a pairlist.
-    pub fn isPairList(&self) -> bool {
-        unsafe { Rf_isPairList(self.get()) != 0 }
-    }
-
-    /// Return true if this is a primitive function.
-    pub fn isPrimitive(&self) -> bool {
-        unsafe { Rf_isPrimitive(self.get()) != 0 }
-    }
-
-    /// Return true if this is a time series vector (see tsp).
-    pub fn isTs(&self) -> bool {
-        unsafe { Rf_isTs(self.get()) != 0 }
-    }
-
-    /// Return true if this is a user defined binop.
-    pub fn isUserBinop(&self) -> bool {
-        unsafe { Rf_isUserBinop(self.get()) != 0 }
-    }
-
-    /// Return true if this is a valid string.
-    pub fn isValidString(&self) -> bool {
-        unsafe { Rf_isValidString(self.get()) != 0 }
-    }
-
-    /// Return true if this is a valid string.
-    pub fn isValidStringF(&self) -> bool {
-        unsafe { Rf_isValidStringF(self.get()) != 0 }
-    }
-
-    /// Return true if this is a vector.
-    pub fn isVector(&self) -> bool {
-        unsafe { Rf_isVector(self.get()) != 0 }
-    }
-
-    /// Return true if this is an atomic vector.
-    pub fn isVectorAtomic(&self) -> bool {
-        unsafe { Rf_isVectorAtomic(self.get()) != 0 }
-    }
-
-    /// Return true if this is a vector list.
-    pub fn isVectorList(&self) -> bool {
-        unsafe { Rf_isVectorList(self.get()) != 0 }
-    }
-
-    /// Return true if this is can be made into a vector.
-    pub fn isVectorizable(&self) -> bool {
-        unsafe { Rf_isVectorizable(self.get()) != 0 }
-    }
-
-    /// Check an external pointer tag
-    /// This may work better by using a symbol cached in a static variable.
-    pub fn check_external_ptr(&self, expected_tag: &str) -> bool {
-        if self.sexptype() == libR_sys::EXTPTRSXP {
-            let tag = unsafe { self.externalPtrTag() };
-            if tag.as_str() == Some(expected_tag) {
-                return true;
-            }
-        }
-        false
-    }
-}
-
-pub unsafe fn new_owned(sexp: SEXP) -> Robj {
-    R_PreserveObject(sexp);
-    Robj::Owned(sexp)
-}
-
-pub unsafe fn new_borrowed(sexp: SEXP) -> Robj {
-    Robj::Borrowed(sexp)
-}
-
-pub unsafe fn new_sys(sexp: SEXP) -> Robj {
-    Robj::Sys(sexp)
-}
-
-/// Compare equality with integer slices.
-impl<'a> PartialEq<[i32]> for Robj {
-    fn eq(&self, rhs: &[i32]) -> bool {
-        self.as_i32_slice() == Some(rhs)
-    }
-}
-
-/// Compare equality with slices of double.
-impl<'a> PartialEq<[f64]> for Robj {
-    fn eq(&self, rhs: &[f64]) -> bool {
-        self.as_f64_slice() == Some(rhs)
-    }
-}
-
-/// Compare equality with strings.
-impl PartialEq<str> for Robj {
-    fn eq(&self, rhs: &str) -> bool {
-        self.as_str() == Some(rhs)
-    }
-}
-
-/// 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 {
-                let lsexp = self.get();
-                let rsexp = rhs.get();
-                match self.sexptype() {
-                    NILSXP => true,
-                    SYMSXP => PRINTNAME(lsexp) == PRINTNAME(rsexp),
-                    LISTSXP | LANGSXP | DOTSXP => self
-                        .pairlist_iter()
-                        .unwrap()
-                        .eq(rhs.pairlist_iter().unwrap()),
-                    CLOSXP => false,
-                    ENVSXP => false,
-                    PROMSXP => false,
-                    SPECIALSXP => false,
-                    BUILTINSXP => false,
-                    CHARSXP => self.as_str() == rhs.as_str(),
-                    LGLSXP => self.as_logical_slice() == rhs.as_logical_slice(),
-                    INTSXP => self.as_i32_slice() == rhs.as_i32_slice(),
-                    REALSXP => self.as_f64_slice() == rhs.as_f64_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()),
-                    BCODESXP => false,
-                    EXTPTRSXP => false,
-                    WEAKREFSXP => false,
-                    RAWSXP => self.as_u8_slice() == rhs.as_u8_slice(),
-                    S4SXP => false,
-                    NEWSXP => false,
-                    FREESXP => false,
-                    _ => false,
-                }
-            }
-        } else {
-            false
-        }
-    }
-}
-
-/// Implement {:?} formatting.
-impl std::fmt::Debug for Robj {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self.sexptype() {
-            NILSXP => write!(f, "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()),
-            LGLSXP => {
-                let slice = self.as_logical_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "{}", if slice[0].0 == 0 { "FALSE" } else { "TRUE" })
-                } else {
-                    write!(f, "&{:?}", slice)
-                }
-            }
-            INTSXP => {
-                let slice = self.as_i32_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "{:?}", slice[0])
-                } else {
-                    write!(f, "{:?}", self.as_i32_slice().unwrap())
-                }
-            }
-            REALSXP => {
-                let slice = self.as_f64_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "{:?}", slice[0])
-                } else {
-                    write!(f, "{:?}", slice)
-                }
-            }
-            VECSXP => write!(f, "{:?}", self.list_iter().unwrap().collect::<Vec<_>>()),
-            EXPRSXP => write!(
-                f,
-                "Expr({:?})",
-                self.list_iter().unwrap().collect::<Vec<_>>()
-            ),
-            WEAKREFSXP => write!(
-                f,
-                "Weakref({:?})",
-                self.list_iter().unwrap().collect::<Vec<_>>()
-            ),
-            // CPLXSXP => false,
-            STRSXP => {
-                write!(f, "[")?;
-                let mut sep = "";
-                for obj in self.str_iter().unwrap() {
-                    write!(f, "{}{:?}", sep, obj)?;
-                    sep = ", ";
-                }
-                write!(f, "]")
-            }
-            // DOTSXP => false,
-            // ANYSXP => false,
-            // VECSXP => false,
-            // EXPRSXP => false,
-            // BCODESXP => false,
-            // EXTPTRSXP => false,
-            // WEAKREFSXP => false,
-            RAWSXP => {
-                let slice = self.as_u8_slice().unwrap();
-                if slice.len() == 1 {
-                    write!(f, "{}", slice[0])
-                } else {
-                    write!(f, "{:?}", slice)
-                }
-            }
-            // S4SXP => false,
-            // NEWSXP => false,
-            // FREESXP => false,
-            _ => write!(f, "??"),
-        }
-    }
-}
-
-// 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 {
-    let mut len = 0;
-    loop {
-        if *ptr.offset(len) == 0 {
-            break;
-        }
-        len += 1;
-    }
-    let slice = std::slice::from_raw_parts(ptr, len as usize);
-    std::str::from_utf8_unchecked(slice)
-}
-
-/// 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) }
-    }
-}
-
-/// Release any owned objects.
-impl Drop for Robj {
-    fn drop(&mut self) {
-        unsafe {
-            match self {
-                Robj::Owned(sexp) => R_ReleaseObject(*sexp),
-                Robj::Borrowed(_) => (),
-                Robj::Sys(_) => (),
-            }
-        }
-    }
-}
-
-/// Convert a null to an Robj.
-impl From<()> for Robj {
-    fn from(_: ()) -> Self {
-        // Note: we do not need to protect this.
-        unsafe { Robj::Sys(R_NilValue) }
-    }
-}
-
-/// Convert a length value to an Robj.
-/// Note: This is good only up to 2^53, but that exceeds the address space
-/// of current generation computers (8PiB)
-impl From<usize> for Robj {
-    fn from(val: usize) -> Self {
-        unsafe {
-            new_owned(if val >= 0x80000000 {
-                Rf_ScalarReal(val as raw::c_double)
-            } else {
-                Rf_ScalarInteger(val as raw::c_int)
-            })
-        }
-    }
-}
-
-/// 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,
-            ))
-        }
-    }
-}
-
-/// Convert a wrapped string ref to an Robj language object.
-impl<'a> From<Lang<'a>> for Robj {
-    fn from(val: Lang<'a>) -> Self {
-        unsafe {
-            let mut name = Vec::from(val.0.as_bytes());
-            name.push(0);
-            new_owned(Rf_lang1(Rf_install(name.as_ptr() as *const raw::c_char)))
-        }
-    }
-}
-
-/// `ToVectorValue` is a trait that allows many different types
-/// to be converted to vectors. It is used as a type parameter
-/// to `collect_robj()`.
-pub trait ToVectorValue {
-    fn sexptype() -> SEXPTYPE {
-        0
-    }
-
-    fn to_numeric(&self) -> f64 where Self: Sized {
-        0.
-    }
-
-    fn to_integer(&self) -> i32 where Self: Sized  {
-        std::i32::MIN
-    }
-
-    fn to_logical(&self) -> i32 where Self: Sized  {
-        std::i32::MIN
-    }
-
-    fn to_raw(&self) -> u8 where Self: Sized  {
-        0
-    }
-
-    fn to_sexp(&self) -> SEXP where Self: Sized  {
-        unsafe { R_NaString }
-    }
-}
-
-macro_rules! impl_numeric_tvv {
-    ($t: ty) => {
-        impl ToVectorValue for $t {
-            fn sexptype() -> SEXPTYPE {
-                REALSXP
-            }
-        
-            fn to_numeric(&self) -> f64 {
-                *self as f64
-            }
-        }
-        
-        impl ToVectorValue for &$t {
-            fn sexptype() -> SEXPTYPE {
-                REALSXP
-            }
-        
-            fn to_numeric(&self) -> f64 {
-                **self as f64
-            }
-        }
-        
-        impl ToVectorValue for Option<$t> {
-            fn sexptype() -> SEXPTYPE {
-                REALSXP
-            }
-        
-            fn to_numeric(&self) -> f64 {
-                if self.is_some() {
-                    self.unwrap() as f64
-                } else {
-                    unsafe { R_NaReal }
-                }
-            }
-        }
-    }
-}
-
-impl_numeric_tvv!(f64);
-impl_numeric_tvv!(f32);
-
-macro_rules! impl_integer_tvv {
-    ($t: ty) => {
-        impl ToVectorValue for $t {
-            fn sexptype() -> SEXPTYPE {
-                INTSXP
-            }
-
-            fn to_integer(&self) -> i32 {
-                *self as i32
-            }
-        }
-
-        impl ToVectorValue for &$t {
-            fn sexptype() -> SEXPTYPE {
-                INTSXP
-            }
-
-            fn to_integer(&self) -> i32 {
-                **self as i32
-            }
-        }
-
-        impl ToVectorValue for Option<$t> {
-            fn sexptype() -> SEXPTYPE {
-                INTSXP
-            }
-
-            fn to_integer(&self) -> i32 {
-                if self.is_some() {
-                    self.unwrap() as i32
-                } else {
-                    unsafe { R_NaInt }
-                }
-            }
-        }
-    }
-}
-
-impl_integer_tvv!(i8);
-impl_integer_tvv!(i16);
-impl_integer_tvv!(i32);
-impl_integer_tvv!(i64);
-impl_integer_tvv!(u16);
-impl_integer_tvv!(u32);
-impl_integer_tvv!(u64);
-
-impl ToVectorValue for &str {
-    fn sexptype() -> SEXPTYPE {
-        STRSXP
-    }
-
-    fn to_sexp(&self) -> SEXP where Self: Sized  {
-        unsafe { Rf_mkCharLen(self.as_ptr() as *const raw::c_char, self.len() as i32) }
-    }
-}
-
-impl ToVectorValue for &&str {
-    fn sexptype() -> SEXPTYPE {
-        STRSXP
-    }
-
-    fn to_sexp(&self) -> SEXP where Self: Sized  {
-        unsafe { Rf_mkCharLen(self.as_ptr() as *const raw::c_char, self.len() as i32) }
-    }
-}
-
-impl ToVectorValue for Option<&str> {
-    fn sexptype() -> SEXPTYPE {
-        STRSXP
-    }
-
-    fn to_sexp(&self) -> SEXP where Self: Sized {
-        if let Some(s) = self {
-            unsafe { Rf_mkCharLen(s.as_ptr() as *const raw::c_char, s.len() as i32) }
-        } else {
-            unsafe { R_NaString }
-        }
-    }
-}
-
-impl ToVectorValue for String {
-    fn sexptype() -> SEXPTYPE {
-        STRSXP
-    }
-
-    fn to_sexp(&self) -> SEXP where Self: Sized  {
-        unsafe { Rf_mkCharLen(self.as_ptr() as *const raw::c_char, self.len() as i32) }
-    }
-}
-
-impl ToVectorValue for bool {
-    fn sexptype() -> SEXPTYPE {
-        LGLSXP
-    }
-
-    fn to_logical(&self) -> i32 where Self: Sized  {
-        *self as i32
-    }
-}
-
-impl ToVectorValue for &bool {
-    fn sexptype() -> SEXPTYPE {
-        LGLSXP
-    }
-
-    fn to_logical(&self) -> i32 where Self: Sized  {
-        **self as i32
-    }
-}
-
-impl ToVectorValue for Option<bool> {
-    fn sexptype() -> SEXPTYPE {
-        LGLSXP
-    }
-
-    fn to_logical(&self) -> i32 {
-        if self.is_some() {
-            self.unwrap() as i32
-        } else {
-            unsafe { R_NaInt }
-        }
-    }
-}
-
-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
-    }
-}
-
-// impl ToVectorValue for Option<u8> {
-//     fn sexptype() -> SEXPTYPE {
-//         RAWSXP
-//     }
-
-//     fn to_raw(&self) -> u8 {
-//         if self.is_some() {
-//             self.unwrap()
-//         } else {
-//             0
-//         }
-//     }
-// }
-
-pub trait RobjItertools : Iterator {
-    /// Convert a wide range of iterators to Robj.
-    /// ```
-    /// use extendr_api::*;
-    ///
-    /// extendr_engine::start_r();
-    ///
-    /// // Integer iterators.
-    /// let robj = (0..3).collect_robj();
-    /// assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
-    ///
-    /// // Logical iterators.
-    /// let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
-    /// assert_eq!(robj.as_logical_vector().unwrap(), vec![true, false, true]);
-    ///
-    /// // Numeric iterators.
-    /// let robj = (0..3).map(|x| x as f64).collect_robj();
-    /// assert_eq!(robj.as_numeric_vector().unwrap(), vec![0., 1., 2.]);
-    ///
-    /// // String iterators.
-    /// let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
-    /// assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
-    /// ```
-    fn collect_robj(self) -> Robj
-        where
-            Self : 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_numeric();
-                                }
-                            }
-                            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 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()
-        }
-    }
-}
-
-// Thanks to *pretzelhammer* on stackoverflow for this.
-impl<T> RobjItertools for T where T: Iterator {}
-
-impl<T> From<T> for Robj
-where
-    T : ToVectorValue
-{
-    fn from(scalar: T) -> Self {
-        Some(scalar).into_iter().collect_robj()
-    }
-}
-
-impl<T> From<&[T]> for Robj
-where
-    T : ToVectorValue + Clone
-{
-    fn from(slice: &[T]) -> Self {
-        slice.into_iter().cloned().collect_robj()
-    }
-}
-
-impl<T> From<Vec<T>> for Robj
-where
-    T : ToVectorValue + Clone
-{
-    fn from(slice: Vec<T>) -> Self {
-        slice.into_iter().collect_robj()
-    }
-}
-
-// Iterator over the objects in a vector or string.
-#[derive(Clone)]
-pub struct VecIter {
-    vector: SEXP,
-    i: usize,
-    len: usize,
-}
-
-impl Iterator for VecIter {
-    type Item = Robj;
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        (self.len, Some(self.len))
-    }
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let i = self.i;
-        self.i += 1;
-        if i >= self.len {
-            return None;
-        } else {
-            Some(Robj::from(unsafe { VECTOR_ELT(self.vector, i as isize) }))
-        }
-    }
-
-    fn nth(&mut self, n: usize) -> Option<Self::Item> {
-        self.i += n;
-        self.next()
-    }
-}
-
-impl std::fmt::Debug for VecIter {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "[")?;
-        for s in self.clone() {
-            write!(f, "{:?}", s)?;
-        }
-        write!(f, "]")
-    }
-}
-
-/// Iterator over the objects in a vector or string.
-pub type IntegerIter<'a> = std::slice::Iter<'a, i32>;
-pub type NumericIter<'a> = std::slice::Iter<'a, f64>;
-pub type LogicalIter<'a> = std::slice::Iter<'a, Bool>;
-
-// Iterator over the objects in a vector or string.
-#[derive(Clone)]
-pub struct ListIter {
-    list_elem: SEXP,
-}
-
-impl ListIter {
-    /// Make an empty list iterator.
-    pub fn new() -> Self {
-        unsafe { Self { list_elem: R_NilValue } }
-    }
-}
-
-impl Iterator for ListIter {
-    type Item = Robj;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        unsafe {
-            let sexp = self.list_elem;
-            if sexp == R_NilValue {
-                None
-            } else {
-                self.list_elem = CDR(sexp);
-                Some(new_borrowed(CAR(sexp)))
-            }
-        }
-    }
-}
-
-impl std::fmt::Debug for ListIter {
-    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)]
-pub struct StrIter {
-    vector: SEXP,
-    i: usize,
-    len: usize,
-    levels: SEXP,
-}
-
-impl StrIter {
-    /// Make an empty str iterator.
-    pub fn new() -> Self {
-        unsafe { Self { vector: R_NilValue, i: 0, len: 0, levels: R_NilValue } }
-    }
-}
-
-// Get a string reference from a CHARSXP
-fn str_from_strsxp<'a>(sexp: SEXP, index: isize) -> &'a str {
-    unsafe {
-        if index < 0 || index >= Rf_xlength(sexp) {
-            ""
-        } else {
-            let charsxp = STRING_ELT(sexp, index);
-            let ptr = R_CHAR(charsxp) as *const u8;
-            let slice = std::slice::from_raw_parts(ptr, Rf_xlength(charsxp) as usize);
-            std::str::from_utf8_unchecked(slice)
-        }
-    }
-}
-
-impl Iterator for StrIter {
-    type Item = &'static str;
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        (self.len, Some(self.len))
-    }
-
-    fn next(&mut self) -> Option<Self::Item> {
-        unsafe {
-            let i = self.i;
-            self.i += 1;
-            if i >= self.len {
-                return None;
-            } else if TYPEOF(self.vector) as u32 == STRSXP {
-                Some(str_from_strsxp(self.vector, i as isize))
-            } else if TYPEOF(self.vector) as u32 == INTSXP && TYPEOF(self.levels) as u32 == STRSXP {
-                let j = *(INTEGER(self.vector).offset(i as isize));
-                Some(str_from_strsxp(self.levels, j as isize - 1))
-            } else {
-                return None;
-            }
-        }
-    }
-
-    fn nth(&mut self, n: usize) -> Option<Self::Item> {
-        self.i += n;
-        self.next()
-    }
-}
-
-impl std::fmt::Debug for StrIter {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "[")?;
-        let mut comma = "";
-        for s in self.clone() {
-            write!(f, "{}{:?}", comma, s)?;
-            comma = ", ";
-        }
-        write!(f, "]")
-    }
-}
-
-type NamedListIter = std::iter::Zip<StrIter, VecIter>;
-
-#[cfg(test)]
-mod tests {
-    use extendr_engine::*;
-    use super::*;
-
-    #[test]
-    fn test_debug() {
-        // Special values
-        assert_eq!(format!("{:?}", Robj::from(NULL)), "NULL");
-        assert_eq!(format!("{:?}", Robj::from(TRUE)), "TRUE");
-        assert_eq!(format!("{:?}", Robj::from(FALSE)), "FALSE");
-
-        // Scalars
-        assert_eq!(format!("{:?}", Robj::from(1)), "1");
-        assert_eq!(format!("{:?}", Robj::from(1.)), "1.0");
-        assert_eq!(format!("{:?}", Robj::from("hello")), "[\"hello\"]");
-        let s = "hello".to_string();
-        assert_eq!(format!("{:?}", Robj::from(s)), "[\"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]"
-        );
-
-        // Wrappers
-        assert_eq!(format!("{:?}", Robj::from(Symbol("x"))), "Symbol(\"x\")");
-        assert_eq!(
-            format!("{:?}", Robj::from(Character("x"))),
-            "Character(\"x\")"
-        );
-        assert_eq!(
-            format!("{:?}", Robj::from(Lang("x"))),
-            "Lang([Symbol(\"x\")])"
-        );
-
-        // Logical
-        assert_eq!(
-            format!("{:?}", Robj::from(&[Bool(1), Bool(0)][..])),
-            "&[Bool(1), Bool(0)]"
-        );
-    }
-
-    #[test]
-    fn test_from_robj() {
-        assert_eq!(<bool>::from_robj(&Robj::from(true)), Ok(true));
-        assert_eq!(<u8>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<u16>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<u32>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<u64>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<i8>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<i16>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<i32>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<i64>::from_robj(&Robj::from(1)), Ok(1));
-        assert_eq!(<f32>::from_robj(&Robj::from(1)), Ok(1.));
-        assert_eq!(<f64>::from_robj(&Robj::from(1)), Ok(1.));
-
-        assert_eq!(<Vec::<i32>>::from_robj(&Robj::from(1)), Ok(vec![1]));
-        assert_eq!(<Vec::<f64>>::from_robj(&Robj::from(1.)), Ok(vec![1.]));
-
-        let hello = Robj::from("hello");
-        assert_eq!(<&str>::from_robj(&hello), Ok("hello"));
-
-        // conversion from a vector to a scalar value
-        assert_eq!(<i32>::from_robj(&Robj::from(vec![].as_slice() as &[i32])), Err("Input must be of length 1. Vector of length zero given."));
-        assert_eq!(<i32>::from_robj(&Robj::from(vec![1].as_slice() as &[i32])), Ok(1));
-        assert_eq!(<i32>::from_robj(&Robj::from(vec![1, 2].as_slice() as &[i32])), Err("Input must be of length 1. Vector of length >1 given."));
-
-        use std::collections::HashMap;
-        let list = Robj::eval_string("list(a=1L, b=2L)").unwrap();
-        let hmap1 = [("a".into(), 1.into()), ("b".into(), 2.into())].iter().cloned().collect::<HashMap<String, Robj>>();
-        let hmap2 = [("a", 1.into()), ("b", 2.into())].iter().cloned().collect::<HashMap<&str, Robj>>();
-        let hmap_owned = <HashMap<String, Robj>>::from_robj(&list).unwrap();
-        let hmap_borrowed = <HashMap<&str, Robj>>::from_robj(&list).unwrap();
-        assert_eq!(hmap_owned, hmap1);
-        assert_eq!(hmap_borrowed, hmap2);
-
-        assert!(hmap_owned["a"].is_owned());
-        assert_eq!(hmap_owned["a"], Robj::from(1));
-        assert_eq!(hmap_owned["b"], Robj::from(2));
-
-        assert_eq!(hmap_borrowed["a"], Robj::from(1));
-        assert_eq!(hmap_borrowed["b"], Robj::from(2));
-
-        let na_integer = Robj::eval_string("NA_integer_").unwrap();
-        assert!(<i32>::from_robj(&na_integer).is_err());
-        assert_eq!(<Option<i32>>::from_robj(&na_integer), Ok(None));
-        assert_eq!(<Option<i32>>::from_robj(&Robj::from(1)), Ok(Some(1)));
-        assert!(<Option<i32>>::from_robj(&Robj::from(&[1, 2][..])).is_err());
-
-        let na_bool = Robj::eval_string("TRUE == NA").unwrap();
-        assert!(<bool>::from_robj(&na_bool).is_err());
-        assert_eq!(<Option<bool>>::from_robj(&na_bool), Ok(None));
-        assert_eq!(<Option<bool>>::from_robj(&Robj::from(true)), Ok(Some(true)));
-        assert!(<Option<bool>>::from_robj(&Robj::from(&[true, false][..])).is_err());
-
-        let na_real = Robj::eval_string("NA_real_").unwrap();
-        assert!(<f64>::from_robj(&na_real).is_err());
-        assert_eq!(<Option<f64>>::from_robj(&na_real), Ok(None));
-        assert_eq!(<Option<f64>>::from_robj(&Robj::from(1.)), Ok(Some(1.)));
-        assert!(<Option<f64>>::from_robj(&Robj::from(&[1., 2.][..])).is_err());
-
-        let na_string = Robj::eval_string("NA_character_").unwrap();
-        assert!(<&str>::from_robj(&na_string).is_err());
-        assert_eq!(<Option<&str>>::from_robj(&na_string), Ok(None));
-        assert_eq!(<Option<&str>>::from_robj(&Robj::from("1")), Ok(Some("1")));
-        assert!(<Option<&str>>::from_robj(&Robj::from(&["1", "2"][..])).is_err());
-
-        let na_string = Robj::eval_string("NA_character_").unwrap();
-        assert!(<String>::from_robj(&na_string).is_err());
-        assert_eq!(<Option<String>>::from_robj(&na_string), Ok(None));
-        assert_eq!(<Option<String>>::from_robj(&Robj::from("1")), Ok(Some("1".to_string())));
-        assert!(<Option<String>>::from_robj(&Robj::from(&["1", "2"][..])).is_err());
-    }
-    #[test]
-    fn test_to_robj() {
-        assert_eq!(Robj::from(true), Robj::from(&[Bool::from(true)][..]));
-        //assert_eq!(Robj::from(1_u8), Robj::from(1));
-        assert_eq!(Robj::from(1_u16), Robj::from(1));
-        assert_eq!(Robj::from(1_u32), Robj::from(1));
-        assert_eq!(Robj::from(1_u64), Robj::from(1));
-        assert_eq!(Robj::from(1_i8), Robj::from(1));
-        assert_eq!(Robj::from(1_i16), Robj::from(1));
-        assert_eq!(Robj::from(1_i32), Robj::from(1));
-        assert_eq!(Robj::from(1_i64), Robj::from(1));
-        assert_eq!(Robj::from(1.0_f32), Robj::from(1.));
-        assert_eq!(Robj::from(1.0_f64), Robj::from(1.));
-
-        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!(Robj::from(Some(1)), Robj::from(1));
-        assert!(!Robj::from(Some(1)).is_na());
-        assert!(Robj::from(<Option<i32>>::None).is_na());
-
-        assert_eq!(Robj::from(Some(true)), Robj::from(true));
-        assert!(!Robj::from(Some(true)).is_na());
-        assert!(Robj::from(<Option<bool>>::None).is_na());
-
-        assert_eq!(Robj::from(Some(1.)), Robj::from(1.));
-        assert!(!Robj::from(Some(1.)).is_na());
-        assert!(Robj::from(<Option<f64>>::None).is_na());
-
-        assert_eq!(Robj::from(Some("xyz")), Robj::from("xyz"));
-        assert!(!Robj::from(Some("xyz")).is_na());
-        assert!(Robj::from(<Option<&str>>::None).is_na());
-    }
-
-    #[test]
-    fn parse_test() -> Result<(), AnyError> {
-        start_r();
-        let p = Robj::parse("print(1L);print(1L);")?;
-        assert_eq!(
-            format!("{:?}", p),
-            "Expr([Lang([Symbol(\"print\"), 1]), Lang([Symbol(\"print\"), 1])])"
-        );
-
-        let p = Robj::eval_string("1L + 1L")?;
-        assert_eq!(p, Robj::from(2));
-        Ok(())
-    }
-
-    #[test]
-    fn output_iterator_test() -> Result<(), AnyError> {
-        start_r();
-
-        // Allocation where size is known in advance.
-        let robj = (0..3).collect_robj();
-        assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
-
-        let robj = [0, 1, 2].iter().collect_robj();
-        assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
-
-        let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
-        assert_eq!(robj.as_logical_vector().unwrap(), vec![true, false, true]);
-
-        let robj = [true, false, true].iter().collect_robj();
-        assert_eq!(robj.as_logical_vector().unwrap(), vec![true, false, true]);
-
-        let robj = (0..3).map(|x| x as f64).collect_robj();
-        assert_eq!(robj.as_numeric_vector().unwrap(), vec![0., 1., 2.]);
-
-        let robj = [0., 1., 2.].iter().collect_robj();
-        assert_eq!(robj.as_numeric_vector().unwrap(), vec![0., 1., 2.]);
-
-        let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
-        assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
-
-        let robj = ["0", "1", "2"].iter().collect_robj();
-        assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
-
-        // Fallback allocation where size is not known in advance.
-        let robj = (0..3).filter(|&x| x != 1).collect_robj();
-        assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 2]);
-
-        let robj = (0..3).filter(|&x| x != 1).map(|x| x as f64).collect_robj();
-        assert_eq!(robj.as_numeric_vector().unwrap(), vec![0., 2.]);
-
-        let robj = (0..3).filter(|&x| x != 1).map(|x| format!("{}", x)).collect_robj();
-        assert_eq!(robj.as_str_vector(), Some(vec!["0", "2"]));
-
-        Ok(())
-    }
-    
-    // Test that we can use Iterators as the input to functions.
-    // eg.
-    // #[extendr]
-    // fn fred(a: NumericIter, b: NumericIter) -> NumericIter {
-    // }
-    #[test]
-    fn input_iterator_test() {
-        start_r();
-
-        let src : &[&str] = &["1", "2", "3"];
-        let robj = Robj::from(src);
-        let iter = <StrIter>::from_robj(&robj).unwrap();
-        assert_eq!(iter.collect::<Vec<_>>(), src);
-
-        let src = &[Robj::from(1), Robj::from(2), Robj::from(3)];
-        let robj = Robj::from(List(src));
-        let iter = <VecIter>::from_robj(&robj).unwrap();
-        assert_eq!(iter.collect::<Vec<_>>(), src);
-
-        let src: &[i32] = &[1, 2, 3];
-        let robj = Robj::from(src);
-        let iter = <IntegerIter>::from_robj(&robj).unwrap();
-        assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
-
-        let src: &[f64] = &[1., 2., 3.];
-        let robj = Robj::from(src);
-        let iter = <NumericIter>::from_robj(&robj).unwrap();
-        assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
-
-        let src: &[Bool] = &[Bool::from(true), Bool::from(false), Bool::from(true)];
-        let robj = Robj::from(src);
-        let iter = <LogicalIter>::from_robj(&robj).unwrap();
-        assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
-    }
-}
diff --git a/extendr-api/src/robj/from_robj.rs b/extendr-api/src/robj/from_robj.rs
new file mode 100644
index 000000000..7a5e0f615
--- /dev/null
+++ b/extendr-api/src/robj/from_robj.rs
@@ -0,0 +1,236 @@
+use super::*;
+
+/// Trait used for incomming parameter conversion.
+pub trait FromRobj<'a>: Sized {
+    // Convert an incomming Robj from R into a value or an error.
+    fn from_robj(_robj: &'a Robj) -> Result<Self, &'static str> {
+        Err("unable to convert value from R object")
+    }
+}
+
+macro_rules! impl_prim_from_robj {
+    ($t: ty) => {
+        impl<'a> FromRobj<'a> for $t {
+            fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+                if let Some(v) = robj.as_integer_slice() {
+                    match v.len() {
+                        0 => Err("Input must be of length 1. Vector of length zero given."),
+                        1 => {
+                            if !v[0].is_na() {
+                                Ok(v[0] as Self)
+                            } else {
+                                Err("Input must not be NA.")
+                            }
+                        }
+                        _ => Err("Input must be of length 1. Vector of length >1 given."),
+                    }
+                } else if let Some(v) = robj.as_real_slice() {
+                    match v.len() {
+                        0 => Err("Input must be of length 1. Vector of length zero given."),
+                        1 => {
+                            if !v[0].is_na() {
+                                Ok(v[0] as Self)
+                            } else {
+                                Err("Input must not be NA.")
+                            }
+                        }
+                        _ => Err("Input must be of length 1. Vector of length >1 given."),
+                    }
+                } else {
+                    Err("unable to convert R object to primitive")
+                }
+            }
+        }
+    };
+}
+
+impl_prim_from_robj!(u8);
+impl_prim_from_robj!(u16);
+impl_prim_from_robj!(u32);
+impl_prim_from_robj!(u64);
+impl_prim_from_robj!(i8);
+impl_prim_from_robj!(i16);
+impl_prim_from_robj!(i32);
+impl_prim_from_robj!(i64);
+impl_prim_from_robj!(f32);
+impl_prim_from_robj!(f64);
+
+impl<'a> FromRobj<'a> for bool {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if let Some(v) = robj.as_logical_slice() {
+            match v.len() {
+                0 => Err("Input must be of length 1. Vector of length zero given."),
+                1 => {
+                    if !v[0].is_na() {
+                        Ok(v[0].to_bool())
+                    } else {
+                        Err("Input must not be NA.")
+                    }
+                }
+                _ => Err("Input must be of length 1. Vector of length >1 given."),
+            }
+        } else {
+            Err("Not a logical object.")
+        }
+    }
+}
+
+impl<'a> FromRobj<'a> for &'a str {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Err("Input must not be NA.")
+        } else if let Some(s) = robj.as_str() {
+            Ok(s)
+        } else {
+            Err("Not a string object.")
+        }
+    }
+}
+
+impl<'a> FromRobj<'a> for String {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Err("Input must not be NA.")
+        } else if let Some(s) = robj.as_str() {
+            Ok(s.to_string())
+        } else {
+            Err("not a string object")
+        }
+    }
+}
+
+impl<'a> FromRobj<'a> for Vec<i32> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if let Some(v) = robj.as_integer_slice() {
+            Ok(Vec::from(v))
+        } else {
+            Err("not an integer or logical vector")
+        }
+    }
+}
+
+impl<'a> FromRobj<'a> for Vec<f64> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if let Some(v) = robj.as_real_slice() {
+            Ok(Vec::from(v))
+        } else {
+            Err("not a floating point vector")
+        }
+    }
+}
+
+macro_rules! impl_iter_from_robj {
+    ($t: ty, $iter_fn: ident, $msg: expr) => {
+        impl<'a> FromRobj<'a> for $t {
+            fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+                if let Some(v) = robj.$iter_fn() {
+                    Ok(v)
+                } else {
+                    Err($msg)
+                }
+            }
+        }
+    };
+}
+
+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.");
+
+/// Pass-through Robj conversion.
+impl<'a> FromRobj<'a> for Robj {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        Ok(unsafe { new_borrowed(robj.get()) })
+    }
+}
+
+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() {
+            Ok(iter
+                .map(|(k, v)| (k.to_string(), v.to_owned()))
+                .collect::<HashMap<String, Robj>>())
+        } else {
+            Err("expected a list")
+        }
+    }
+}
+
+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() {
+            Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
+        } else {
+            Err("expected a list")
+        }
+    }
+}
+
+// NA-sensitive integer input handling
+impl<'a> FromRobj<'a> for Option<i32> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Ok(None)
+        } else if let Some(val) = robj.as_integer() {
+            Ok(Some(val))
+        } else {
+            Err("expected an integer scalar")
+        }
+    }
+}
+
+// NA-sensitive logical input handling
+impl<'a> FromRobj<'a> for Option<bool> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if let Some(val) = robj.as_logical() {
+            if val.is_na() {
+                Ok(None)
+            } else {
+                Ok(Some(val.is_true()))
+            }
+        } else {
+            Err("expected a logical scalar")
+        }
+    }
+}
+
+// NA-sensitive real input handling
+impl<'a> FromRobj<'a> for Option<f64> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Ok(None)
+        } else if let Some(val) = robj.as_real() {
+            Ok(Some(val))
+        } else {
+            Err("expected a real scalar")
+        }
+    }
+}
+
+// NA-sensitive string input handling
+impl<'a> FromRobj<'a> for Option<&'a str> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Ok(None)
+        } else if let Some(val) = robj.as_str() {
+            Ok(Some(val))
+        } else {
+            Err("expected a character scalar")
+        }
+    }
+}
+
+// NA-sensitive string input handling
+impl<'a> FromRobj<'a> for Option<String> {
+    fn from_robj(robj: &'a Robj) -> Result<Self, &'static str> {
+        if robj.is_na() {
+            Ok(None)
+        } else if let Some(val) = robj.as_str() {
+            Ok(Some(val.to_string()))
+        } else {
+            Err("expected a character scalar")
+        }
+    }
+}
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
new file mode 100644
index 000000000..414e7ed12
--- /dev/null
+++ b/extendr-api/src/robj/into_robj.rs
@@ -0,0 +1,513 @@
+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) }
+    }
+}
+
+/// Convert a null to an Robj.
+impl From<()> for Robj {
+    fn from(_: ()) -> Self {
+        // Note: we do not need to protect this.
+        unsafe { Robj::Sys(R_NilValue) }
+    }
+}
+
+/// 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))
+        })
+    }
+}
+
+/// Convert an Robj reference into a borrowed Robj.
+impl From<&Robj> for Robj {
+    // Note: we should probably have a much better reference
+    // mechanism as double-free or underprotection is a distinct possibility.
+    fn from(val: &Robj) -> Self {
+        unsafe { new_borrowed(val.get()) }
+    }
+}
+
+/// `ToVectorValue` is a trait that allows many different types
+/// to be converted to vectors. It is used as a type parameter
+/// to `collect_robj()`.
+pub trait ToVectorValue {
+    fn sexptype() -> SEXPTYPE {
+        0
+    }
+
+    fn to_real(&self) -> f64
+    where
+        Self: Sized,
+    {
+        0.
+    }
+
+    fn to_integer(&self) -> i32
+    where
+        Self: Sized,
+    {
+        std::i32::MIN
+    }
+
+    fn to_logical(&self) -> i32
+    where
+        Self: Sized,
+    {
+        std::i32::MIN
+    }
+
+    fn to_raw(&self) -> u8
+    where
+        Self: Sized,
+    {
+        0
+    }
+
+    fn to_sexp(&self) -> SEXP
+    where
+        Self: Sized,
+    {
+        unsafe { R_NaString }
+    }
+}
+
+macro_rules! impl_real_tvv {
+    ($t: ty) => {
+        impl ToVectorValue for $t {
+            fn sexptype() -> SEXPTYPE {
+                REALSXP
+            }
+
+            fn to_real(&self) -> f64 {
+                *self as f64
+            }
+        }
+
+        impl ToVectorValue for &$t {
+            fn sexptype() -> SEXPTYPE {
+                REALSXP
+            }
+
+            fn to_real(&self) -> f64 {
+                **self as f64
+            }
+        }
+
+        impl ToVectorValue for Option<$t> {
+            fn sexptype() -> SEXPTYPE {
+                REALSXP
+            }
+
+            fn to_real(&self) -> f64 {
+                if self.is_some() {
+                    self.unwrap() as f64
+                } else {
+                    unsafe { R_NaReal }
+                }
+            }
+        }
+    };
+}
+
+impl_real_tvv!(f64);
+impl_real_tvv!(f32);
+impl_real_tvv!(usize);
+
+macro_rules! impl_integer_tvv {
+    ($t: ty) => {
+        impl ToVectorValue for $t {
+            fn sexptype() -> SEXPTYPE {
+                INTSXP
+            }
+
+            fn to_integer(&self) -> i32 {
+                *self as i32
+            }
+        }
+
+        impl ToVectorValue for &$t {
+            fn sexptype() -> SEXPTYPE {
+                INTSXP
+            }
+
+            fn to_integer(&self) -> i32 {
+                **self as i32
+            }
+        }
+
+        impl ToVectorValue for Option<$t> {
+            fn sexptype() -> SEXPTYPE {
+                INTSXP
+            }
+
+            fn to_integer(&self) -> i32 {
+                if self.is_some() {
+                    self.unwrap() as i32
+                } else {
+                    unsafe { R_NaInt }
+                }
+            }
+        }
+    };
+}
+
+impl_integer_tvv!(i8);
+impl_integer_tvv!(i16);
+impl_integer_tvv!(i32);
+impl_integer_tvv!(i64);
+impl_integer_tvv!(u16);
+impl_integer_tvv!(u32);
+impl_integer_tvv!(u64);
+
+macro_rules! impl_str_tvv {
+    ($t: ty) => {
+        impl ToVectorValue for $t {
+            fn sexptype() -> SEXPTYPE {
+                STRSXP
+            }
+
+            fn to_sexp(&self) -> SEXP
+            where
+                Self: Sized,
+            {
+                str_to_character(self.as_ref())
+            }
+        }
+
+        impl ToVectorValue for &$t {
+            fn sexptype() -> SEXPTYPE {
+                STRSXP
+            }
+
+            fn to_sexp(&self) -> SEXP
+            where
+                Self: Sized,
+            {
+                str_to_character(self.as_ref())
+            }
+        }
+
+        impl ToVectorValue for Option<$t> {
+            fn sexptype() -> SEXPTYPE {
+                STRSXP
+            }
+
+            fn to_sexp(&self) -> SEXP
+            where
+                Self: Sized,
+            {
+                if let Some(s) = self {
+                    str_to_character(s.as_ref())
+                } else {
+                    unsafe { R_NaString }
+                }
+            }
+        }
+    };
+}
+
+impl_str_tvv! {&str}
+impl_str_tvv! {String}
+
+impl ToVectorValue for bool {
+    fn sexptype() -> SEXPTYPE {
+        LGLSXP
+    }
+
+    fn to_logical(&self) -> i32
+    where
+        Self: Sized,
+    {
+        *self as i32
+    }
+}
+
+impl ToVectorValue for &bool {
+    fn sexptype() -> SEXPTYPE {
+        LGLSXP
+    }
+
+    fn to_logical(&self) -> i32
+    where
+        Self: Sized,
+    {
+        **self as i32
+    }
+}
+
+impl ToVectorValue for Bool {
+    fn sexptype() -> SEXPTYPE {
+        LGLSXP
+    }
+
+    fn to_logical(&self) -> i32
+    where
+        Self: Sized,
+    {
+        self.0
+    }
+}
+
+impl ToVectorValue for &Bool {
+    fn sexptype() -> SEXPTYPE {
+        LGLSXP
+    }
+
+    fn to_logical(&self) -> i32
+    where
+        Self: Sized,
+    {
+        self.0
+    }
+}
+
+impl ToVectorValue for Option<bool> {
+    fn sexptype() -> SEXPTYPE {
+        LGLSXP
+    }
+
+    fn to_logical(&self) -> i32 {
+        if self.is_some() {
+            self.unwrap() as i32
+        } else {
+            unsafe { R_NaInt }
+        }
+    }
+}
+
+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
+    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.
+    /// ```
+    /// use extendr_api::*;
+    ///
+    /// extendr_engine::start_r();
+    ///
+    /// // Integer iterators.
+    /// let robj = (0..3).collect_robj();
+    /// assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
+    ///
+    /// // Logical iterators.
+    /// let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
+    /// assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, TRUE]);
+    ///
+    /// // Numeric iterators.
+    /// let robj = (0..3).map(|x| x as f64).collect_robj();
+    /// assert_eq!(robj.as_real_vector().unwrap(), vec![0., 1., 2.]);
+    ///
+    /// // String iterators.
+    /// let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
+    /// assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
+    /// ```
+    fn collect_robj(self) -> Robj
+    where
+        Self: Iterator,
+        Self: Sized,
+        Self::Item: ToVectorValue,
+    {
+        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()
+    }
+}
+
+// Thanks to *pretzelhammer* on stackoverflow for this.
+impl<T> RobjItertools for T where T: Iterator {}
+
+// Scalars which are ToVectorValue
+impl<T> From<T> for Robj
+where
+    T: ToVectorValue,
+{
+    fn from(scalar: T) -> Self {
+        Some(scalar).into_iter().collect_robj()
+    }
+}
+
+// We would love to do a blanket IntoIterator impl.
+// But the matching rules would clash with the above.
+macro_rules! impl_from_iter {
+    ($t: ty) => {
+        impl<'a, T> From<$t> for Robj
+        where
+            Self: 'a,
+            T: Clone + 'a,
+            T: ToVectorValue,
+        {
+            fn from(val: $t) -> Self {
+                val.iter().cloned().collect_robj()
+            }
+        }
+    };
+}
+
+macro_rules! impl_from_into_iter {
+    ($t: ty) => {
+        impl<'a, T> From<$t> for Robj
+        where
+            Self: 'a,
+            T: 'a,
+            &'a T: ToVectorValue,
+        {
+            fn from(val: $t) -> Self {
+                val.into_iter().collect_robj()
+            }
+        }
+    };
+}
+
+macro_rules! impl_from_as_iterator {
+    ($t: ty) => {
+        impl<T> From<$t> for Robj
+        where
+            $t: RobjItertools,
+            <$t as Iterator>::Item: ToVectorValue,
+            T: ToVectorValue,
+        {
+            fn from(val: $t) -> Self {
+                val.collect_robj()
+            }
+        }
+    };
+}
+
+// impl<T> From<Range<T>> for Robj
+// where
+//     Range<T> : RobjItertools,
+//     <Range<T> as Iterator>::Item: ToVectorValue,
+//     T : ToVectorValue
+// {
+//     fn from(val: Range<T>) -> Self {
+//         val.collect_robj()
+//     }
+// }
+
+// Template constants are still unstable in rust.
+impl_from_iter! {[T; 1]}
+impl_from_iter! {[T; 2]}
+impl_from_iter! {[T; 3]}
+impl_from_iter! {[T; 4]}
+impl_from_iter! {[T; 5]}
+impl_from_iter! {[T; 6]}
+impl_from_iter! {[T; 7]}
+impl_from_iter! {[T; 8]}
+impl_from_iter! {[T; 9]}
+impl_from_iter! {[T; 10]}
+impl_from_iter! {[T; 11]}
+impl_from_iter! {[T; 12]}
+impl_from_iter! {[T; 13]}
+impl_from_iter! {[T; 14]}
+impl_from_iter! {[T; 15]}
+impl_from_iter! {[T; 16]}
+impl_from_iter! {[T; 17]}
+impl_from_iter! {[T; 18]}
+impl_from_iter! {[T; 19]}
+impl_from_iter! {Vec<T>}
+
+impl_from_into_iter! {&'a [T]}
+
+impl_from_as_iterator! {Range<T>}
+impl_from_as_iterator! {RangeInclusive<T>}
diff --git a/extendr-api/src/robj/iter.rs b/extendr-api/src/robj/iter.rs
new file mode 100644
index 000000000..bba5eca96
--- /dev/null
+++ b/extendr-api/src/robj/iter.rs
@@ -0,0 +1,305 @@
+use super::*;
+
+// Iterator over the objects in a vector or string.
+#[derive(Clone)]
+pub struct VecIter {
+    vector: SEXP,
+    i: usize,
+    len: usize,
+}
+
+impl Iterator for VecIter {
+    type Item = Robj;
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.len, Some(self.len))
+    }
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let i = self.i;
+        self.i += 1;
+        if i >= self.len {
+            return None;
+        } else {
+            Some(Robj::from(unsafe { VECTOR_ELT(self.vector, i as isize) }))
+        }
+    }
+
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.i += n;
+        self.next()
+    }
+}
+
+impl std::fmt::Debug for VecIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "[")?;
+        for s in self.clone() {
+            write!(f, "{:?}", s)?;
+        }
+        write!(f, "]")
+    }
+}
+
+/// Iterator over name-value pairs in lists.
+pub type NamedListIter = std::iter::Zip<StrIter, VecIter>;
+
+/// Iterator over primitives in integer objects.
+pub type IntegerIter<'a> = std::slice::Iter<'a, i32>;
+
+/// Iterator over primitives in real objects.
+pub type RealIter<'a> = std::slice::Iter<'a, f64>;
+
+/// Iterator over primitives in logical objects.
+pub type LogicalIter<'a> = std::slice::Iter<'a, Bool>;
+
+/// Iterator over the objects in a vector or string.
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// let my_list = list!(a = 1, b = 2);
+/// let mut total = 0;
+/// for robj in my_list.list_iter().unwrap() {
+///   if let Some(val) = robj.as_integer() {
+///     total += val;
+///   }
+/// }
+/// assert_eq!(total, 3);
+///
+/// for name in my_list.names().unwrap() {
+///    assert!(name == "a" || name == "b")
+/// }
+/// ```
+#[derive(Clone)]
+pub struct ListIter {
+    list_elem: SEXP,
+}
+
+impl ListIter {
+    /// Make an empty list iterator.
+    pub fn new() -> Self {
+        unsafe {
+            Self {
+                list_elem: R_NilValue,
+            }
+        }
+    }
+}
+
+impl Iterator for ListIter {
+    type Item = Robj;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let sexp = self.list_elem;
+            if sexp == R_NilValue {
+                None
+            } else {
+                self.list_elem = CDR(sexp);
+                Some(new_borrowed(CAR(sexp)))
+            }
+        }
+    }
+}
+
+impl std::fmt::Debug for ListIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "[")?;
+        for s in self.clone() {
+            write!(f, "{:?}", s)?;
+        }
+        write!(f, "]")
+    }
+}
+/// Iterator over strings or string factors.
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// 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"]);
+///
+/// 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"]);
+/// ```
+#[derive(Clone)]
+pub struct StrIter {
+    vector: SEXP,
+    i: usize,
+    len: usize,
+    levels: SEXP,
+}
+
+impl StrIter {
+    /// Make an empty str iterator.
+    pub fn new() -> Self {
+        unsafe {
+            Self {
+                vector: R_NilValue,
+                i: 0,
+                len: 0,
+                levels: R_NilValue,
+            }
+        }
+    }
+}
+
+// Get a string reference from a CHARSXP
+fn str_from_strsxp<'a>(sexp: SEXP, index: isize) -> &'a str {
+    unsafe {
+        if index < 0 || index >= Rf_xlength(sexp) {
+            ""
+        } else {
+            let charsxp = STRING_ELT(sexp, index);
+            let ptr = R_CHAR(charsxp) as *const u8;
+            let slice = std::slice::from_raw_parts(ptr, Rf_xlength(charsxp) as usize);
+            std::str::from_utf8_unchecked(slice)
+        }
+    }
+}
+
+impl Iterator for StrIter {
+    type Item = &'static str;
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.len, Some(self.len))
+    }
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let i = self.i;
+            self.i += 1;
+            if i >= self.len {
+                return None;
+            } else if TYPEOF(self.vector) as u32 == STRSXP {
+                Some(str_from_strsxp(self.vector, i as isize))
+            } else if TYPEOF(self.vector) as u32 == INTSXP && TYPEOF(self.levels) as u32 == STRSXP {
+                let j = *(INTEGER(self.vector).offset(i as isize));
+                Some(str_from_strsxp(self.levels, j as isize - 1))
+            } else {
+                return None;
+            }
+        }
+    }
+
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.i += n;
+        self.next()
+    }
+}
+
+impl std::fmt::Debug for StrIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "[")?;
+        let mut comma = "";
+        for s in self.clone() {
+            write!(f, "{}{:?}", comma, s)?;
+            comma = ", ";
+        }
+        write!(f, "]")
+    }
+}
+
+impl Robj {
+    /// Get an iterator over a pairlist, lang or ... object.
+    /// Note: we plan to combine list iterators into one.
+    /// ```
+    /// 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)])
+    /// ```
+    pub fn pairlist_iter(&self) -> Option<ListIter> {
+        match self.sexptype() {
+            LISTSXP | LANGSXP | DOTSXP => unsafe {
+                Some(ListIter {
+                    list_elem: self.get(),
+                })
+            },
+            _ => None,
+        }
+    }
+
+    /// Get an iterator over an unnamed list.
+    /// Note: we plan to combine list iterators into one.
+    /// ```
+    /// 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();
+    /// assert_eq!(objects, vec![r!(1), r!(2), r!(3)])
+    /// ```
+    pub fn list_iter(&self) -> Option<VecIter> {
+        match self.sexptype() {
+            VECSXP | EXPRSXP | WEAKREFSXP => unsafe {
+                Some(VecIter {
+                    vector: self.get(),
+                    i: 0,
+                    len: self.len(),
+                })
+            },
+            _ => None,
+        }
+    }
+
+    /// Get an iterator over a string vector.
+    /// Returns None if the object is not a string vector
+    /// but works for factors.
+    ///
+    /// ```
+    /// use extendr_api::*;
+    ///
+    /// extendr_engine::start_r();
+    ///
+    /// let obj = Robj::from(vec!["a", "b", "c"]);
+    /// assert_eq!(obj.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"]);
+    /// ```
+    pub fn str_iter(&self) -> Option<StrIter> {
+        let i = 0;
+        let len = self.len();
+        match self.sexptype() {
+            STRSXP => unsafe {
+                let vector = self.get();
+                Some(StrIter {
+                    vector,
+                    i,
+                    len,
+                    levels: R_NilValue,
+                })
+            },
+            INTSXP => unsafe {
+                let vector = self.get();
+                if let Some(levels) = self.get_attrib(levels_symbol()) {
+                    if self.is_factor() && levels.sexptype() == STRSXP {
+                        Some(StrIter {
+                            vector,
+                            i,
+                            len,
+                            levels: levels.get(),
+                        })
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                }
+            },
+            _ => None,
+        }
+    }
+}
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
new file mode 100644
index 000000000..d0070c525
--- /dev/null
+++ b/extendr-api/src/robj/mod.rs
@@ -0,0 +1,1129 @@
+//! R object handling.
+//!
+//! See. https://cran.r-project.org/doc/manuals/R-exts.html
+//!
+//! Fundamental principals:
+//!
+//! * Any function that can break the protection mechanism is unsafe.
+//! * Users should be able to do almost everything without using libR_sys.
+//! * The interface should be friendly to R users without Rust experience.
+//!
+
+use libR_sys::*;
+use std::os::raw;
+
+use crate::logical::*;
+use crate::wrapper::*;
+use crate::{single_threaded, AnyError, IsNA};
+
+use std::collections::HashMap;
+use std::iter::IntoIterator;
+use std::ops::{Range, RangeInclusive};
+
+mod from_robj;
+mod into_robj;
+mod iter;
+mod rinternals;
+mod symbols;
+
+#[cfg(test)]
+mod tests;
+
+pub use from_robj::*;
+pub use into_robj::*;
+pub use iter::*;
+pub use rinternals::*;
+pub use symbols::*;
+
+/// Wrapper for an R S-expression pointer (SEXP).
+///
+/// Create R objects from rust types and iterators:
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// // Different ways of making integer scalar 1.
+/// let non_na : Option<i32> = Some(1);
+/// let a : Robj = vec![1].into();
+/// let b = r!(1);
+/// let c = r!(vec![1]);
+/// let d = r!(non_na);
+/// let e = r!([1]);
+/// assert_eq!(a, b);
+/// assert_eq!(a, c);
+/// assert_eq!(a, d);
+/// assert_eq!(a, e);
+///
+/// // Different ways of making boolean scalar TRUE.
+/// let a : Robj = true.into();
+/// let b = r!(TRUE);
+/// assert_eq!(a, b);
+///
+/// // Create a named list
+/// let a = list!(a = 1, b = "x");
+/// assert_eq!(a.len(), 2);
+///
+/// // Use an iterator (like 1:10)
+/// let a = r!(1 ..= 10);
+/// assert_eq!(a, r!([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
+///
+/// // Use an iterator (like (1:10)[(1:10) %% 3 == 0])
+/// let a = (1 ..= 10).filter(|v| v % 3 == 0).collect_robj();
+/// assert_eq!(a, c!(3, 6, 9));
+/// ```
+///
+/// Use iterators to get the contents of R objects.
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// let a : Robj = c!(1, 2, 3, 4, 5);
+/// let iter = a.integer_iter().unwrap();
+/// let robj = iter.filter(|&&x| x < 3).collect_robj();
+/// assert_eq!(robj, c!(1, 2));
+/// ```
+///
+/// Convert to/from Rust vectors.
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// let a : Robj = r!(vec![1., 2., 3., 4.]);
+/// let b : Vec<f64> = a.as_real_vector().unwrap();
+/// assert_eq!(a.len(), 4);
+/// assert_eq!(b, vec![1., 2., 3., 4.]);
+/// ```
+///
+/// Iterate over names and values.
+///
+/// ```
+/// use extendr_api::*;        // Put API in scope.
+/// extendr_engine::start_r(); // Start test environment.
+///
+/// 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();
+/// assert_eq!(names, vec!["a", "b", "c"]);
+/// assert_eq!(names_and_values, vec![("a", r!(1)), ("b", r!("x")), ("c", r!(vec![1, 2]))]);
+/// ```
+///
+/// NOTE: as much as possible we wish to make this object safe (ie. no segfaults).
+///
+/// If you avoid using unsafe functions it is more likely that you will avoid
+/// panics and segfaults. We will take great trouble to ensure that this
+/// is true.
+///
+pub enum Robj {
+    // This object owns the SEXP and must free it.
+    #[doc(hidden)]
+    Owned(SEXP),
+
+    //  This object references a SEXP such as an input parameter.
+    #[doc(hidden)]
+    Borrowed(SEXP),
+
+    //  This object references a SEXP owned by libR.
+    #[doc(hidden)]
+    Sys(SEXP),
+}
+
+/// TRUE value eg. `r!(TRUE)`
+pub const TRUE: Bool = Bool(1);
+
+/// FALSE value eg. `r!(FALSE)`
+pub const FALSE: Bool = Bool(0);
+
+/// NULL value eg. `r!(NULL)`
+pub const NULL: () = ();
+
+/// NA value for integers eg. `r!(NA_INTEGER)`
+pub const NA_INTEGER: Option<i32> = None;
+
+/// NA value for real values eg. `r!(NA_REAL)`
+pub const NA_REAL: Option<f64> = None;
+
+/// NA value for strings. `r!(NA_STRING)`
+pub const NA_STRING: Option<&str> = None;
+
+/// NA value for logical. `r!(NA_LOGICAL)`
+pub const NA_LOGICAL: Bool = Bool(std::i32::MIN);
+
+impl Clone for Robj {
+    fn clone(&self) -> Self {
+        self.duplicate()
+    }
+}
+
+impl Default for Robj {
+    fn default() -> Self {
+        Robj::from(())
+    }
+}
+
+impl Robj {
+    /// Get a copy of the underlying SEXP.
+    /// Note: this is unsafe.
+    #[doc(hidden)]
+    pub unsafe fn get(&self) -> SEXP {
+        match self {
+            Robj::Owned(sexp) => *sexp,
+            Robj::Borrowed(sexp) => *sexp,
+            Robj::Sys(sexp) => *sexp,
+        }
+    }
+
+    /// Get a copy of the underlying SEXP for mutable types.
+    /// This is valid only for owned objects as we are not
+    /// permitted to modify parameters or system objects.
+    #[doc(hidden)]
+    pub unsafe fn get_mut(&mut self) -> Option<SEXP> {
+        match self {
+            Robj::Owned(sexp) => Some(*sexp),
+            Robj::Borrowed(_) => None,
+            Robj::Sys(_) => None,
+        }
+    }
+
+    #[doc(hidden)]
+    /// Get the XXXSXP type of the object.
+    pub fn sexptype(&self) -> u32 {
+        unsafe { TYPEOF(self.get()) as u32 }
+    }
+
+    /// Get the extended length of the object.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let a : Robj = r!(vec![1., 2., 3., 4.]);
+    /// assert_eq!(a.len(), 4);
+    /// ```
+    pub fn len(&self) -> usize {
+        unsafe { Rf_xlength(self.get()) as usize }
+    }
+
+    /// Is this object is an NA scalar?
+    /// Works for character, integer and numeric types.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// assert_eq!(r!(NA_INTEGER).is_na(), true);
+    /// assert_eq!(r!(NA_REAL).is_na(), true);
+    /// assert_eq!(r!(NA_STRING).is_na(), true);
+    /// ```
+    pub fn is_na(&self) -> bool {
+        if self.len() != 1 {
+            false
+        } else {
+            unsafe {
+                let sexp = self.get();
+                match self.sexptype() {
+                    STRSXP => STRING_ELT(sexp, 0) == libR_sys::R_NaString,
+                    INTSXP => *(INTEGER(sexp)) == libR_sys::R_NaInt,
+                    LGLSXP => *(LOGICAL(sexp)) == libR_sys::R_NaInt,
+                    REALSXP => R_IsNA(*(REAL(sexp))) != 0,
+                    _ => false,
+                }
+            }
+        }
+    }
+
+    /// Get a read-only reference to the content of an integer vector.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([1, 2, 3]);
+    /// assert_eq!(robj.as_integer_slice().unwrap(), [1, 2, 3]);
+    /// ```
+    pub fn as_integer_slice<'a>(&self) -> Option<&'a [i32]> {
+        self.as_typed_slice()
+    }
+
+    /// Get an iterator over integer elements of this slice.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([1, 2, 3]);
+    /// let mut tot = 0;
+    /// for val in robj.integer_iter().unwrap() {
+    ///   tot += val;
+    /// }
+    /// assert_eq!(tot, 6);
+    /// ```
+    pub fn integer_iter<'a>(&self) -> Option<IntegerIter<'a>>
+    where
+        Self: 'a,
+    {
+        if let Some(slice) = self.as_integer_slice() {
+            Some(slice.iter())
+        } else {
+            None
+        }
+    }
+
+    /// Get a Vec<i32> copied from the object.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([1, 2, 3]);
+    /// assert_eq!(robj.as_integer_slice().unwrap(), vec![1, 2, 3]);
+    /// ```
+    pub fn as_integer_vector(&self) -> Option<Vec<i32>> {
+        if let Some(value) = self.as_integer_slice() {
+            Some(value.iter().cloned().collect::<Vec<_>>())
+        } else {
+            None
+        }
+    }
+
+    /// Get a read-only reference to the content of a logical vector
+    /// using the tri-state [Bool]. Returns None if not a logical vector.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([TRUE, FALSE, NA_LOGICAL]);
+    /// assert_eq!(robj.as_logical_slice().unwrap(), [TRUE, FALSE, NA_LOGICAL]);
+    /// ```
+    pub fn as_logical_slice(&self) -> Option<&[Bool]> {
+        self.as_typed_slice()
+    }
+
+    /// Get a Vec<Bool> copied from the object
+    /// using the tri-state [Bool].
+    /// Returns None if not a logical vector.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([TRUE, FALSE, NA_LOGICAL]);
+    /// assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, NA_LOGICAL]);
+    /// ```
+    pub fn as_logical_vector(&self) -> Option<Vec<Bool>> {
+        if let Some(value) = self.as_logical_slice() {
+            Some(value.iter().cloned().collect::<Vec<_>>())
+        } else {
+            None
+        }
+    }
+
+    /// Get an iterator over logical elements of this slice.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([TRUE, FALSE, NA_LOGICAL]);
+    /// let (mut nt, mut nf, mut nna) = (0, 0, 0);
+    /// for val in robj.logical_iter().unwrap() {
+    ///   match *val {
+    ///     TRUE => nt += 1,
+    ///     FALSE => nf += 1,
+    ///     NA_LOGICAL => nna += 1,
+    ///     _ => ()
+    ///   }
+    /// }
+    /// assert_eq!((nt, nf, nna), (1, 1, 1));
+    /// ```
+    pub fn logical_iter(&self) -> Option<LogicalIter> {
+        if let Some(slice) = self.as_logical_slice() {
+            Some(slice.iter())
+        } else {
+            None
+        }
+    }
+
+    /// Get a read-only reference to the content of a double vector.
+    /// Note: the slice may contain NaN or NA values.
+    /// We may introduce a "Real" type to handle this like the Bool type.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([Some(1.), None, Some(3.)]);
+    /// let mut tot = 0.;
+    /// for val in robj.as_real_slice().unwrap() {
+    ///   if !val.is_na() {
+    ///     tot += val;
+    ///   }
+    /// }
+    /// assert_eq!(tot, 4.);
+    /// ```
+    pub fn as_real_slice(&self) -> Option<&[f64]> {
+        self.as_typed_slice()
+    }
+
+    /// Get an iterator over real elements of this slice.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([1., 2., 3.]);
+    /// let mut tot = 0.;
+    /// for val in robj.real_iter().unwrap() {
+    ///   if !val.is_na() {
+    ///     tot += val;
+    ///   }
+    /// }
+    /// assert_eq!(tot, 6.);
+    /// ```
+    pub fn real_iter(&self) -> Option<RealIter> {
+        if let Some(slice) = self.as_real_slice() {
+            Some(slice.iter())
+        } else {
+            None
+        }
+    }
+
+    /// Get a Vec<f64> copied from the object.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let robj = r!([1., 2., 3.]);
+    /// assert_eq!(robj.as_real_vector().unwrap(), vec![1., 2., 3.])
+    /// ```
+    pub fn as_real_vector(&self) -> Option<Vec<f64>> {
+        if let Some(value) = self.as_real_slice() {
+            Some(value.iter().cloned().collect::<Vec<_>>())
+        } else {
+            None
+        }
+    }
+
+    /// Get a read-only reference to the content of an integer or logical vector.
+    /// ```
+    /// 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]);
+    /// ```
+    pub fn as_raw_slice(&self) -> Option<&[u8]> {
+        self.as_typed_slice()
+    }
+
+    /// Get a read-write reference to the content of an integer or logical vector.
+    /// Note that rust slices are 0-based so `slice[1]` is the middle value.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let mut robj = r!([1, 2, 3]);
+    /// let slice : & mut [i32] = robj.as_integer_slice_mut().unwrap();
+    /// slice[1] = 100;
+    /// assert_eq!(robj, r!([1, 100, 3]));
+    /// ```
+    pub fn as_integer_slice_mut(&mut self) -> Option<&mut [i32]> {
+        self.as_typed_slice_mut()
+    }
+
+    /// Get a read-write reference to the content of a double vector.
+    /// Note that rust slices are 0-based so `slice[1]` is the middle value.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let mut robj = r!([1.0, 2.0, 3.0]);
+    /// let slice = robj.as_real_slice_mut().unwrap();
+    /// slice[1] = 100.0;
+    /// assert_eq!(robj, r!([1.0, 100.0, 3.0]));
+    /// ```
+    pub fn as_real_slice_mut(&mut self) -> Option<&mut [f64]> {
+        self.as_typed_slice_mut()
+    }
+
+    /// Get a read-write reference to the content of a raw vector.
+    /// ```
+    /// use extendr_api::*;        // Put API in scope.
+    /// extendr_engine::start_r(); // Start test environment.
+    ///
+    /// let mut robj = r!([1_u8, 2, 3]);
+    /// let slice = robj.as_raw_slice_mut().unwrap();
+    /// slice[1] = 100;
+    /// assert_eq!(robj, r!([1_u8, 100, 3]));
+    /// ```
+    pub fn as_raw_slice_mut(&mut self) -> Option<&mut [u8]> {
+        self.as_typed_slice_mut()
+    }
+
+    /// Get a vector of owned strings.
+    /// Owned strings have long lifetimes, but are much slower than references.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from("xyz");
+    ///    assert_eq!(robj1.as_string_vector(), Some(vec!["xyz".to_string()]));
+    ///    let robj2 = Robj::from(1);
+    ///    assert_eq!(robj2.as_string_vector(), None);
+    /// ```
+    pub fn as_string_vector(&self) -> Option<Vec<String>> {
+        if let Some(iter) = self.str_iter() {
+            Some(iter.map(str::to_string).collect())
+        } else {
+            None
+        }
+    }
+
+    /// Get a vector of string references.
+    /// String references (&str) are faster, but have short lifetimes.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from("xyz");
+    ///    assert_eq!(robj1.as_str_vector(), Some(vec!["xyz"]));
+    ///    let robj2 = Robj::from(1);
+    ///    assert_eq!(robj2.as_str_vector(), None);
+    /// ```
+    pub fn as_str_vector(&self) -> Option<Vec<&str>> {
+        if let Some(iter) = self.str_iter() {
+            Some(iter.collect())
+        } else {
+            None
+        }
+    }
+
+    /// Get a read-only reference to a char, symbol or string type.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from("xyz");
+    ///    let robj2 = Robj::from(1);
+    ///    assert_eq!(robj1.as_str(), Some("xyz"));
+    ///    assert_eq!(robj2.as_str(), None);
+    /// ```
+    pub fn as_str(&self) -> Option<&str> {
+        unsafe {
+            match self.sexptype() {
+                STRSXP => {
+                    if self.len() != 1 {
+                        None
+                    } else {
+                        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)),
+                _ => None,
+            }
+        }
+    }
+
+    /// Get a scalar integer.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from("xyz");
+    ///    let robj2 = Robj::from(1);
+    ///    let robj3 = Robj::from(NA_INTEGER);
+    ///    assert_eq!(robj1.as_integer(), None);
+    ///    assert_eq!(robj2.as_integer(), Some(1));
+    ///    assert_eq!(robj3.as_integer(), None);
+    /// ```
+    pub fn as_integer(&self) -> Option<i32> {
+        match self.as_integer_slice() {
+            Some(slice) if slice.len() == 1 && !slice[0].is_na() => Some(slice[0]),
+            _ => None,
+        }
+    }
+
+    /// Get a scalar real.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from(1);
+    ///    let robj2 = Robj::from(1.);
+    ///    let robj3 = Robj::from(NA_REAL);
+    ///    assert_eq!(robj1.as_real(), None);
+    ///    assert_eq!(robj2.as_real(), Some(1.));
+    ///    assert_eq!(robj3.as_real(), None);
+    /// ```
+    pub fn as_real(&self) -> Option<f64> {
+        match self.as_real_slice() {
+            Some(slice) if slice.len() == 1 && !slice[0].is_na() => Some(slice[0]),
+            _ => None,
+        }
+    }
+
+    /// Get a scalar rust boolean.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from(TRUE);
+    ///    let robj2 = Robj::from(1.);
+    ///    let robj3 = Robj::from(NA_LOGICAL);
+    ///    assert_eq!(robj1.as_bool(), Some(true));
+    ///    assert_eq!(robj2.as_bool(), None);
+    ///    assert_eq!(robj3.as_bool(), None);
+    /// ```
+    pub fn as_bool(&self) -> Option<bool> {
+        match self.as_logical_slice() {
+            Some(slice) if slice.len() == 1 && !slice[0].is_na() => Some(slice[0].into()),
+            _ => None,
+        }
+    }
+
+    /// Get a scalar boolean as a tri-boolean [Bool] value.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let robj1 = Robj::from(TRUE);
+    ///    let robj2 = Robj::from([TRUE, FALSE]);
+    ///    let robj3 = Robj::from(NA_LOGICAL);
+    ///    assert_eq!(robj1.as_logical(), Some(TRUE));
+    ///    assert_eq!(robj2.as_logical(), None);
+    ///    assert_eq!(robj3.as_logical(), Some(NA_LOGICAL));
+    /// ```
+    pub fn as_logical(&self) -> Option<Bool> {
+        match self.as_logical_slice() {
+            Some(slice) if slice.len() == 1 => Some(slice[0]),
+            _ => None,
+        }
+    }
+
+    /// Evaluate the expression in R and return an error or an R object.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let add = lang!("+", 1, 2);
+    ///    assert_eq!(add.eval().unwrap(), r!(3));
+    /// ```
+    pub fn eval(&self) -> Result<Robj, AnyError> {
+        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 {
+                Err(AnyError::from("R eval error"))
+            } else {
+                Ok(Robj::from(res))
+            }
+        })
+    }
+
+    /// Evaluate the expression and return NULL or an R object.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let bad = lang!("imnotavalidfunctioninR", 1, 2);
+    ///    assert_eq!(bad.eval_blind(), r!(NULL));
+    /// ```
+    pub fn eval_blind(&self) -> Robj {
+        let res = self.eval();
+        if res.is_err() {
+            Robj::from(())
+        } else {
+            Robj::from(res.unwrap())
+        }
+    }
+
+    /// Parse a string into an R executable object
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let expr = Robj::parse("1 + 2").unwrap();
+    ///    assert!(expr.is_expression());
+    /// ```
+    pub fn parse(code: &str) -> Result<Robj, AnyError> {
+        single_threaded(|| unsafe {
+            use libR_sys::*;
+            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));
+            match status {
+                1 => Ok(parsed),
+                _ => Err(AnyError::from("parse_error")),
+            }
+        })
+    }
+
+    /// Parse a string into an R executable object and run it.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let res = Robj::eval_string("1 + 2").unwrap();
+    ///    assert_eq!(res, r!(3.));
+    /// ```
+    pub fn eval_string(code: &str) -> Result<Robj, AnyError> {
+        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)
+        })
+    }
+
+    /// Unprotect an object - assumes a transfer of ownership.
+    /// This is unsafe because the object pointer may be left dangling.
+    #[doc(hidden)]
+    pub unsafe fn unprotected(self) -> Robj {
+        match self {
+            Robj::Owned(sexp) => {
+                single_threaded(|| R_ReleaseObject(sexp));
+                Robj::Borrowed(sexp)
+            }
+            _ => self,
+        }
+    }
+
+    /// Return true if the object is owned by this wrapper.
+    /// If so, it will be released when the wrapper drops.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///    let owned = r!(1);      // Allocated vector.
+    ///    let borrowed = r!(());  // R_NilValue
+    ///    assert_eq!(owned.is_owned(), true);
+    ///    assert_eq!(borrowed.is_owned(), false);
+    /// ```
+    pub fn is_owned(&self) -> bool {
+        match self {
+            Robj::Owned(_) => true,
+            _ => false,
+        }
+    }
+
+    // Convert the Robj to an owned one.
+    #[doc(hidden)]
+    pub fn to_owned(self) -> Robj {
+        match self {
+            Robj::Owned(_) => self,
+            _ => unsafe { new_owned(self.get()) },
+        }
+    }
+}
+
+/// Generic access to typed slices in an Robj.
+pub trait AsTypedSlice<'a, T>
+where
+    Self: 'a,
+{
+    fn as_typed_slice(&self) -> Option<&'a [T]>
+    where
+        Self: 'a,
+    {
+        None
+    }
+
+    fn as_typed_slice_mut(&mut self) -> Option<&'a mut [T]>
+    where
+        Self: 'a,
+    {
+        None
+    }
+}
+
+macro_rules! make_typed_slice {
+    ($type: ty, $fn: tt, $($sexp: tt),* ) => {
+        impl<'a> AsTypedSlice<'a, $type> for Robj
+        where
+            Self : 'a,
+        {
+            fn as_typed_slice(&self) -> Option<&'a [$type]> {
+                match self.sexptype() {
+                    $( $sexp )|* => {
+                        unsafe {
+                            let ptr = $fn(self.get()) as *const $type;
+                            Some(std::slice::from_raw_parts(ptr, self.len()))
+                        }
+                    }
+                    _ => None
+                }
+            }
+
+            fn as_typed_slice_mut(&mut self) -> Option<&'a mut [$type]> {
+                match self.sexptype() {
+                    $( $sexp )|* => {
+                        unsafe {
+                            let ptr = $fn(self.get()) as *mut $type;
+                            Some(std::slice::from_raw_parts_mut(ptr, self.len()))
+                        }
+                    }
+                    _ => None
+                }
+            }
+        }
+    }
+}
+
+make_typed_slice!(Bool, INTEGER, LGLSXP);
+make_typed_slice!(i32, INTEGER, INTSXP);
+make_typed_slice!(f64, REAL, REALSXP);
+make_typed_slice!(u8, RAW, RAWSXP);
+
+/// These are helper functions which give access to common properties of R objects.
+#[allow(non_snake_case)]
+impl Robj {
+    /// Get a specific attribute as a borrowed robj if it exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let mut robj = r!("hello");
+    ///    robj.set_attrib(Symbol("xyz"), 1);
+    ///    assert_eq!(robj.get_attrib(Symbol("xyz")), Some(r!(1)));
+    /// ```
+    pub fn get_attrib<'a, N>(&self, name: N) -> Option<Robj>
+    where
+        Self: 'a,
+        Robj: From<N> + 'a,
+    {
+        let name = Robj::from(name);
+        if self.sexptype() == CHARSXP {
+            None
+        } else {
+            let res = unsafe { new_borrowed(Rf_getAttrib(self.get(), name.get())) };
+            if res.is_null() {
+                None
+            } else {
+                Some(res)
+            }
+        }
+    }
+
+    /// Set a specific attribute and return the value.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let mut robj = r!("hello");
+    ///    let value = robj.set_attrib(Symbol("xyz"), 1);
+    ///    assert_eq!(robj.get_attrib(Symbol("xyz")), Some(value));
+    /// ```
+    pub fn set_attrib<N, V>(&mut self, name: N, value: V) -> Robj
+    where
+        Robj: From<N>,
+        Robj: From<V>,
+    {
+        let name = Robj::from(name);
+        let value = Robj::from(value);
+        single_threaded(||unsafe { new_borrowed(Rf_setAttrib(self.get(), name.get(), value.get())) })
+    }
+
+    /// Get the names attribute as a string iterator if one exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let list = list!(a = 1, b = 2, c = 3);
+    ///    let names : Vec<_> = list.names().unwrap().collect();
+    ///    assert_eq!(names, vec!["a", "b", "c"]);
+    /// ```
+    pub fn names(&self) -> Option<StrIter> {
+        if let Some(names) = self.get_attrib(names_symbol()) {
+            names.str_iter()
+        } else {
+            None
+        }
+    }
+
+    /// Get the dim attribute as an integer iterator if one exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let array = R!(array(data = c(1, 2, 3, 4), dim = c(2, 2), dimnames = list(c("x", "y"), c("a","b")))).unwrap();
+    ///    let dim : Vec<_> = array.dim().unwrap().cloned().collect();
+    ///    assert_eq!(dim, vec![2, 2]);
+    /// ```
+    pub fn dim<'a>(&self) -> Option<IntegerIter<'a>>
+    where
+        Self: 'a,
+        Robj: 'a,
+    {
+        if let Some(dim) = self.get_attrib(dim_symbol()) {
+            dim.integer_iter()
+        } else {
+            None
+        }
+    }
+
+    /// Get the dimnames attribute as a list iterator if one exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let array = R!(array(data = c(1, 2, 3, 4), dim = c(2, 2), dimnames = list(c("x", "y"), c("a","b")))).unwrap();
+    ///    let names : Vec<_> = array.dimnames().unwrap().collect();
+    ///    assert_eq!(names, vec![r!(["x", "y"]), r!(["a", "b"])]);
+    /// ```
+    pub fn dimnames(&self) -> Option<VecIter> {
+        if let Some(names) = self.get_attrib(dimnames_symbol()) {
+            names.list_iter()
+        } else {
+            None
+        }
+    }
+
+    /// Return an iterator over names and values of a list if they exist.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let list = list!(a = 1, b = 2, c = 3);
+    ///    let names_and_values : Vec<_> = list.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> {
+        if let Some(names) = self.names() {
+            if let Some(values) = self.list_iter() {
+                return Some(names.zip(values));
+            }
+        }
+        None
+    }
+
+    /// Get the class attribute as a string iterator if one exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let formula = R!(y ~ A * x + b).unwrap();
+    ///    let class : Vec<_> = formula.class().unwrap().collect();
+    ///    assert_eq!(class, ["formula"]);
+    /// ```
+    pub fn class(&self) -> Option<StrIter> {
+        if let Some(class) = self.get_attrib(class_symbol()) {
+            class.str_iter()
+        } else {
+            None
+        }
+    }
+
+    /// Return true if this class inherits this class.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let formula = R!(y ~ A * x + b).unwrap();
+    ///    assert_eq!(formula.inherits("formula"), true);
+    /// ```
+    pub fn inherits(&self, classname: &str) -> bool {
+        if let Some(mut iter) = self.class() {
+            iter.find(|&n| n == classname).is_some()
+        } else {
+            false
+        }
+    }
+
+    /// Get the levels attribute as a string iterator if one exists.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    let factor = factor!(vec!["abcd", "def", "fg", "fg"]);
+    ///    let levels : Vec<_> = factor.levels().unwrap().collect();
+    ///    assert_eq!(levels, vec!["abcd", "def", "fg"]);
+    /// ```
+    pub fn levels(&self) -> Option<StrIter> {
+        if let Some(levels) = self.get_attrib(levels_symbol()) {
+            levels.str_iter()
+        } else {
+            None
+        }
+    }
+}
+
+#[doc(hidden)]
+pub unsafe fn new_owned(sexp: SEXP) -> Robj {
+    single_threaded(|| R_PreserveObject(sexp));
+    Robj::Owned(sexp)
+}
+
+#[doc(hidden)]
+pub unsafe fn new_borrowed(sexp: SEXP) -> Robj {
+    Robj::Borrowed(sexp)
+}
+
+#[doc(hidden)]
+pub unsafe fn new_sys(sexp: SEXP) -> Robj {
+    Robj::Sys(sexp)
+}
+
+/// Compare equality with integer slices.
+impl<'a> PartialEq<[i32]> for Robj {
+    fn eq(&self, rhs: &[i32]) -> bool {
+        self.as_integer_slice() == Some(rhs)
+    }
+}
+
+/// Compare equality with slices of double.
+impl<'a> PartialEq<[f64]> for Robj {
+    fn eq(&self, rhs: &[f64]) -> bool {
+        self.as_real_slice() == Some(rhs)
+    }
+}
+
+/// Compare equality with strings.
+impl PartialEq<str> for Robj {
+    fn eq(&self, rhs: &str) -> bool {
+        self.as_str() == Some(rhs)
+    }
+}
+
+/// 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 {
+                let lsexp = self.get();
+                let rsexp = rhs.get();
+                match self.sexptype() {
+                    NILSXP => true,
+                    SYMSXP => PRINTNAME(lsexp) == PRINTNAME(rsexp),
+                    LISTSXP | LANGSXP | DOTSXP => self
+                        .pairlist_iter()
+                        .unwrap()
+                        .eq(rhs.pairlist_iter().unwrap()),
+                    CLOSXP => false,
+                    ENVSXP => false,
+                    PROMSXP => false,
+                    SPECIALSXP => false,
+                    BUILTINSXP => false,
+                    CHARSXP => self.as_str() == rhs.as_str(),
+                    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()),
+                    BCODESXP => false,
+                    EXTPTRSXP => false,
+                    WEAKREFSXP => false,
+                    RAWSXP => self.as_raw_slice() == rhs.as_raw_slice(),
+                    S4SXP => false,
+                    NEWSXP => false,
+                    FREESXP => false,
+                    _ => false,
+                }
+            }
+        } else {
+            false
+        }
+    }
+}
+
+/// Implement {:?} formatting.
+impl std::fmt::Debug for Robj {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self.sexptype() {
+            NILSXP => write!(f, "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()),
+            LGLSXP => {
+                let slice = self.as_logical_slice().unwrap();
+                if slice.len() == 1 {
+                    write!(f, "{}", if slice[0].0 == 0 { "FALSE" } else { "TRUE" })
+                } else {
+                    write!(f, "&{:?}", slice)
+                }
+            }
+            INTSXP => {
+                let slice = self.as_integer_slice().unwrap();
+                if slice.len() == 1 {
+                    write!(f, "{:?}", slice[0])
+                } else {
+                    write!(f, "{:?}", self.as_integer_slice().unwrap())
+                }
+            }
+            REALSXP => {
+                let slice = self.as_real_slice().unwrap();
+                if slice.len() == 1 {
+                    write!(f, "{:?}", slice[0])
+                } else {
+                    write!(f, "{:?}", slice)
+                }
+            }
+            VECSXP => write!(f, "{:?}", self.list_iter().unwrap().collect::<Vec<_>>()),
+            EXPRSXP => write!(
+                f,
+                "Expr({:?})",
+                self.list_iter().unwrap().collect::<Vec<_>>()
+            ),
+            WEAKREFSXP => write!(
+                f,
+                "Weakref({:?})",
+                self.list_iter().unwrap().collect::<Vec<_>>()
+            ),
+            // CPLXSXP => false,
+            STRSXP => {
+                write!(f, "[")?;
+                let mut sep = "";
+                for obj in self.str_iter().unwrap() {
+                    write!(f, "{}{:?}", sep, obj)?;
+                    sep = ", ";
+                }
+                write!(f, "]")
+            }
+            // DOTSXP => false,
+            // ANYSXP => false,
+            // VECSXP => false,
+            // EXPRSXP => false,
+            // BCODESXP => false,
+            // EXTPTRSXP => false,
+            // WEAKREFSXP => false,
+            RAWSXP => {
+                let slice = self.as_raw_slice().unwrap();
+                if slice.len() == 1 {
+                    write!(f, "{}", slice[0])
+                } else {
+                    write!(f, "{:?}", slice)
+                }
+            }
+            // S4SXP => false,
+            // NEWSXP => false,
+            // FREESXP => false,
+            _ => write!(f, "??"),
+        }
+    }
+}
+
+// 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 {
+    let mut len = 0;
+    loop {
+        if *ptr.offset(len) == 0 {
+            break;
+        }
+        len += 1;
+    }
+    let slice = std::slice::from_raw_parts(ptr, len as usize);
+    std::str::from_utf8_unchecked(slice)
+}
+
+/// Release any owned objects.
+impl Drop for Robj {
+    fn drop(&mut self) {
+        unsafe {
+            match self {
+                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
new file mode 100644
index 000000000..7d0ea7cf4
--- /dev/null
+++ b/extendr-api/src/robj/rinternals.rs
@@ -0,0 +1,494 @@
+use crate::*;
+use super::*;
+
+///////////////////////////////////////////////////////////////
+/// 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 {
+        unsafe { Rf_isNull(self.get()) != 0 }
+    }
+
+    /// Return true if this is a symbol.
+    pub fn is_symbol(&self) -> bool {
+        unsafe { Rf_isSymbol(self.get()) != 0 }
+    }
+
+    /// Return true if this is a boolean (logical) vector
+    pub fn is_logical(&self) -> bool {
+        unsafe { Rf_isLogical(self.get()) != 0 }
+    }
+
+    /// Return true if this is a real (f64) vector.
+    pub fn is_real(&self) -> bool {
+        unsafe { Rf_isReal(self.get()) != 0 }
+    }
+
+    /// Return true if this is a complex vector.
+    pub fn is_complex(&self) -> bool {
+        unsafe { Rf_isComplex(self.get()) != 0 }
+    }
+
+    /// Return true if this is an expression.
+    pub fn is_expression(&self) -> bool {
+        unsafe { Rf_isExpression(self.get()) != 0 }
+    }
+
+    /// Return true if this is an environment.
+    pub fn is_environment(&self) -> bool {
+        unsafe { Rf_isEnvironment(self.get()) != 0 }
+    }
+
+    /// Return true if this is a string.
+    pub fn is_string(&self) -> bool {
+        unsafe { Rf_isString(self.get()) != 0 }
+    }
+
+    /// Return true if this is an object.
+    pub fn is_object(&self) -> bool {
+        unsafe { Rf_isObject(self.get()) != 0 }
+    }
+
+    /// Get the source ref.
+    pub fn get_current_srcref(val: i32) -> Robj {
+        unsafe { new_owned(R_GetCurrentSrcref(val as raw::c_int)) }
+    }
+
+    /// Get the source filename.
+    pub fn get_src_filename(&self) -> Robj {
+        unsafe { new_owned(R_GetSrcFilename(self.get())) }
+    }
+
+    /// Convert to a string vector.
+    pub fn as_char(&self) -> Robj {
+        unsafe { new_owned(Rf_asChar(self.get())) }
+    }
+
+    /// Convert to vectors of many kinds.
+    pub fn coerce_vector(&self, sexptype: u32) -> Robj {
+        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 {
+        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 {
+        single_threaded(|| unsafe { new_owned(Rf_VectorToPairList(self.get())) })
+    }
+
+    /// Convert a factor to a string vector.
+    pub fn as_character_factor(&self) -> Robj {
+        single_threaded(|| unsafe { new_owned(Rf_asCharacterFactor(self.get())) })
+    }
+
+    /// Allocate a matrix object.
+    pub fn alloc_matrix(sexptype: SEXPTYPE, rows: i32, cols: i32) -> Robj {
+        single_threaded(|| unsafe { new_owned(Rf_allocMatrix(sexptype, rows, cols)) })
+    }
+
+    /* TODO:
+    int Rf_asLogical2(SEXP x, int checking, SEXP call, SEXP rho);
+    Rcomplex Rf_asComplex(SEXP x);
+    void Rf_addMissingVarsToNewEnv(SEXP, SEXP);
+    SEXP Rf_alloc3DArray(SEXPTYPE, int, int, int);
+    SEXP Rf_allocArray(SEXPTYPE, SEXP);
+    SEXP Rf_allocFormalsList2(SEXP sym1, SEXP sym2);
+    SEXP Rf_allocFormalsList3(SEXP sym1, SEXP sym2, SEXP sym3);
+    SEXP Rf_allocFormalsList4(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4);
+    SEXP Rf_allocFormalsList5(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4, SEXP sym5);
+    SEXP Rf_allocFormalsList6(SEXP sym1, SEXP sym2, SEXP sym3, SEXP sym4, SEXP sym5, SEXP sym6);
+    SEXP Rf_allocList(int);
+    SEXP Rf_allocS4Object(void);
+    SEXP Rf_allocSExp(SEXPTYPE);
+    SEXP Rf_allocVector3(SEXPTYPE, R_xlen_t, R_allocator_t*);
+    R_xlen_t Rf_any_duplicated(SEXP x, Rboolean from_last);
+    R_xlen_t Rf_any_duplicated3(SEXP x, SEXP incomp, Rboolean from_last);
+    SEXP Rf_applyClosure(SEXP, SEXP, SEXP, SEXP, SEXP);
+    SEXP Rf_arraySubscript(int, SEXP, SEXP, SEXP (*)(SEXP,SEXP), SEXP (*)(SEXP, int), SEXP);
+    SEXP Rf_classgets(SEXP, SEXP);
+    SEXP Rf_cons(SEXP, SEXP);
+    SEXP Rf_fixSubset3Args(SEXP, SEXP, SEXP, SEXP*);
+    void Rf_copyMatrix(SEXP, SEXP, Rboolean);
+    void Rf_copyListMatrix(SEXP, SEXP, Rboolean);
+    void Rf_copyMostAttrib(SEXP, SEXP);
+    void Rf_copyVector(SEXP, SEXP);
+    int Rf_countContexts(int, int);
+    SEXP Rf_CreateTag(SEXP);
+    void Rf_defineVar(SEXP, SEXP, SEXP);
+    SEXP Rf_dimgets(SEXP, SEXP);
+    SEXP Rf_dimnamesgets(SEXP, SEXP);
+    SEXP Rf_DropDims(SEXP);
+    */
+
+    /// Compatible way to duplicate an object. Use obj.clone() instead
+    /// for Rust compatibility.
+    pub fn duplicate(&self) -> Self {
+        single_threaded(|| unsafe { new_owned(Rf_duplicate(self.get())) })
+    }
+
+    /*
+    SEXP Rf_shallow_duplicate(SEXP);
+    SEXP R_duplicate_attr(SEXP);
+    SEXP R_shallow_duplicate_attr(SEXP);
+    SEXP Rf_lazy_duplicate(SEXP);
+    SEXP Rf_duplicated(SEXP, Rboolean);
+    Rboolean R_envHasNoSpecialSymbols(SEXP);
+    SEXP Rf_eval(SEXP, SEXP);
+    SEXP Rf_ExtractSubset(SEXP, SEXP, SEXP);
+    SEXP Rf_findFun(SEXP, SEXP);
+    SEXP Rf_findFun3(SEXP, SEXP, SEXP);
+    void Rf_findFunctionForBody(SEXP);
+    SEXP Rf_findVar(SEXP, SEXP);
+    SEXP Rf_findVarInFrame(SEXP, SEXP);
+    SEXP Rf_findVarInFrame3(SEXP, SEXP, Rboolean);
+    */
+
+    /// Find a function in an environment ignoring other variables.
+    /// ```
+    ///    use extendr_api::*;
+    ///    extendr_engine::start_r();
+    ///
+    ///    R!(my_fun <- function() {});
+    ///    let my_fun = global_env().find_function(Symbol("my_fun")).unwrap();
+    ///    assert_eq!(my_fun.is_function(), true);
+    /// ```
+    pub fn find_function<S>(&self, symbol: S) -> Result<Robj, AnyError>
+    where
+        Robj: From<S>,
+    {
+        let symbol = Robj::from(symbol);
+        if !symbol.is_symbol() {
+            return Err("find_fun needs a Symbol. eg. find_fun(Symbol(\"xyz\"))".into());
+        }
+        if !self.is_environment() {
+            return Err("find_fun needs an environment.".into());
+        }
+        Ok(single_threaded(|| unsafe { new_borrowed(Rf_findFun(symbol.get(), self.get())) }))
+    }
+
+    /*
+    SEXP Rf_GetArrayDimnames(SEXP);
+    SEXP Rf_GetColNames(SEXP);
+    void Rf_GetMatrixDimnames(SEXP, SEXP*, SEXP*, const char**, const char**);
+    SEXP Rf_GetOption(SEXP, SEXP);
+    SEXP Rf_GetOption1(SEXP);
+    int Rf_FixupDigits(SEXP, warn_type);
+    int Rf_FixupWidth (SEXP, warn_type);
+    int Rf_GetOptionDigits(void);
+    int Rf_GetOptionWidth(void);
+    SEXP Rf_GetRowNames(SEXP);
+    void Rf_gsetVar(SEXP, SEXP, SEXP);
+    SEXP Rf_install(const char *);
+    SEXP Rf_installChar(SEXP);
+    SEXP Rf_installNoTrChar(SEXP);
+    SEXP Rf_installTrChar(SEXP);
+    SEXP Rf_installDDVAL(int i);
+    SEXP Rf_installS3Signature(const char *, const char *);
+    Rboolean Rf_isFree(SEXP);
+    Rboolean Rf_isOrdered(SEXP);
+    Rboolean Rf_isUnmodifiedSpecSym(SEXP sym, SEXP env);
+    Rboolean Rf_isUnordered(SEXP);
+    Rboolean Rf_isUnsorted(SEXP, Rboolean);
+    SEXP Rf_lengthgets(SEXP, R_len_t);
+    SEXP Rf_xlengthgets(SEXP, R_xlen_t);
+    SEXP R_lsInternal(SEXP, Rboolean);
+    SEXP R_lsInternal3(SEXP, Rboolean, Rboolean);
+    SEXP Rf_match(SEXP, SEXP, int);
+    SEXP Rf_matchE(SEXP, SEXP, int, SEXP);
+    SEXP Rf_namesgets(SEXP, SEXP);
+    SEXP Rf_mkChar(const char *);
+    SEXP Rf_mkCharLen(const char *, int);
+    Rboolean Rf_NonNullStringMatch(SEXP, SEXP);
+    */
+
+    /// Number of columns of a matrix
+    pub fn ncols(&self) -> usize {
+        unsafe { Rf_ncols(self.get()) as usize }
+    }
+
+    /// Number of rows of a matrix
+    pub fn nrows(&self) -> usize {
+        unsafe { Rf_nrows(self.get()) as usize }
+    }
+
+    /*SEXP Rf_nthcdr(SEXP, int);
+    Rboolean Rf_pmatch(SEXP, SEXP, Rboolean);
+    Rboolean Rf_psmatch(const char *, const char *, Rboolean);
+    void Rf_PrintValue(SEXP);
+    void Rf_printwhere(void);
+    void Rf_readS3VarsFromFrame(SEXP, SEXP*, SEXP*, SEXP*, SEXP*, SEXP*, SEXP*);
+    SEXP Rf_setAttrib(SEXP, SEXP, SEXP);
+    void Rf_setSVector(SEXP*, int, SEXP);
+    void Rf_setVar(SEXP, SEXP, SEXP);
+    SEXP Rf_stringSuffix(SEXP, int);
+    SEXPTYPE Rf_str2type(const char *);
+    Rboolean Rf_StringBlank(SEXP);
+    SEXP Rf_substitute(SEXP,SEXP);
+    SEXP Rf_topenv(SEXP, SEXP);
+    const char * Rf_translateChar(SEXP);
+    const char * Rf_translateChar0(SEXP);
+    const char * Rf_translateCharUTF8(SEXP);
+    const char * Rf_type2char(SEXPTYPE);
+    SEXP Rf_type2rstr(SEXPTYPE);
+    SEXP Rf_type2str(SEXPTYPE);
+    SEXP Rf_type2str_nowarn(SEXPTYPE);
+    SEXP R_GetCurrentEnv();
+    Rboolean Rf_isS4(SEXP);
+    SEXP Rf_asS4(SEXP, Rboolean, int);
+    SEXP Rf_S3Class(SEXP);
+    int Rf_isBasicClass(const char *);
+    Rboolean R_cycle_detected(SEXP s, SEXP child);
+    u32 Rf_getCharCE(SEXP);
+    SEXP Rf_mkCharCE(const char *, cetype_t);
+    SEXP Rf_mkCharLenCE(const char *, int, cetype_t);
+    SEXP R_forceAndCall(SEXP e, int n, SEXP rho);
+    */
+
+    #[doc(hidden)]
+    pub unsafe fn makeExternalPtr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
+        Robj::make_external_ptr(p, tag, prot)
+    }
+
+    /// 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(),
+        )))
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn externalPtrAddr<T>(&self) -> *mut T {
+        R_ExternalPtrAddr(self.get()) as *mut T
+    }
+
+    /// Internal function used to implement `#[extendr]` impl
+    #[doc(hidden)]
+    pub unsafe fn external_ptr_addr<T>(&self) -> *mut T {
+        R_ExternalPtrAddr(self.get()) as *mut T
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn externalPtrTag(&self) -> Self {
+        self.external_ptr_tag()
+    }
+
+    /// Internal function used to implement `#[extendr]` impl
+    #[doc(hidden)]
+    pub unsafe fn external_ptr_tag(&self) -> Self {
+        new_borrowed(R_ExternalPtrTag(self.get()))
+    }
+
+    /// Internal function used to implement `#[extendr]` impl
+    #[doc(hidden)]
+    pub unsafe fn external_ptr_protected(&self) -> Self {
+        new_borrowed(R_ExternalPtrProtected(self.get()))
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn registerCFinalizer(&self, func: R_CFinalizer_t) {
+        single_threaded(|| self.register_c_finalizer(func))
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn register_c_finalizer(&self, func: R_CFinalizer_t) {
+        single_threaded(|| R_RegisterCFinalizer(self.get(), func));
+    }
+
+    // SEXP R_ExternalPtrTag(SEXP s);
+    // SEXP R_ExternalPtrProtected(SEXP s);
+    // void R_ClearExternalPtr(SEXP s);
+    // void R_SetExternalPtrAddr(SEXP s, void *p);
+    // void R_SetExternalPtrTag(SEXP s, SEXP tag);
+    // void R_SetExternalPtrProtected(SEXP s, SEXP p);
+
+    /*
+    SEXP R_MakeWeakRef(SEXP key, SEXP val, SEXP fin, Rboolean onexit);
+    SEXP R_MakeWeakRefC(SEXP key, SEXP val, R_CFinalizer_t fin, Rboolean onexit);
+    SEXP R_WeakRefKey(SEXP w);
+    SEXP R_WeakRefValue(SEXP w);
+    void R_RunWeakRefFinalizer(SEXP w);
+    SEXP R_PromiseExpr(SEXP);
+    SEXP R_ClosureExpr(SEXP);
+    SEXP R_BytecodeExpr(SEXP e);
+    SEXP R_bcEncode(SEXP);
+    SEXP R_bcDecode(SEXP);
+    void R_registerBC(SEXP, SEXP);
+    Rboolean R_checkConstants(Rboolean);
+    Rboolean R_BCVersionOK(SEXP);
+    void R_RestoreHashCount(SEXP rho);
+    Rboolean R_IsPackageEnv(SEXP rho);
+    SEXP R_PackageEnvName(SEXP rho);
+    SEXP R_FindPackageEnv(SEXP info);
+    Rboolean R_IsNamespaceEnv(SEXP rho);
+    SEXP R_NamespaceEnvSpec(SEXP rho);
+    SEXP R_FindNamespace(SEXP info);
+    void R_LockEnvironment(SEXP env, Rboolean bindings);
+    Rboolean R_EnvironmentIsLocked(SEXP env);
+    void R_LockBinding(SEXP sym, SEXP env);
+    void R_unLockBinding(SEXP sym, SEXP env);
+    void R_MakeActiveBinding(SEXP sym, SEXP fun, SEXP env);
+    Rboolean R_BindingIsLocked(SEXP sym, SEXP env);
+    Rboolean R_BindingIsActive(SEXP sym, SEXP env);
+    Rboolean R_HasFancyBindings(SEXP rho);
+    */
+
+    /// Read-only access to attribute list.
+    // fn attrib(&self) -> Robj {
+    // unsafe {new_borrowed(ATTRIB(self.get()))}
+    // }
+
+    /// Copy a vector and resize it.
+    /// See. https://github.com/hadley/r-internals/blob/master/vectors.md
+    pub fn xlengthgets(&self, new_len: usize) -> Result<Robj, AnyError> {
+        unsafe {
+            if self.is_vector() {
+                Ok(single_threaded(|| new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t))))
+            } else {
+                Err(AnyError::from("xlengthgets: Not a vector type"))
+            }
+        }
+    }
+
+    /// Allocated an owned object of a certain type.
+    pub fn alloc_vector(sexptype: u32, len: usize) -> Robj {
+        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 {
+        single_threaded(|| unsafe { Rf_conformable(a.get(), b.get()) != 0 })
+    }
+
+    /// Borrow an element from a list.
+    // 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 *);
+
+    /// Return true if this is an array.
+    pub fn is_array(&self) -> bool {
+        unsafe { Rf_isArray(self.get()) != 0 }
+    }
+
+    /// Return true if this is factor.
+    pub fn is_factor(&self) -> bool {
+        unsafe { Rf_isFactor(self.get()) != 0 }
+    }
+
+    /// Return true if this is a data frame.
+    pub fn is_frame(&self) -> bool {
+        unsafe { Rf_isFrame(self.get()) != 0 }
+    }
+
+    /// Return true if this is a function.
+    pub fn is_function(&self) -> bool {
+        unsafe { Rf_isFunction(self.get()) != 0 }
+    }
+
+    /// Return true if this is an integer vector.
+    pub fn is_integer(&self) -> bool {
+        unsafe { Rf_isInteger(self.get()) != 0 }
+    }
+
+    /// Return true if this is a language object.
+    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 {
+        unsafe { Rf_isList(self.get()) != 0 }
+    }
+
+    /// Return true if this is a matrix.
+    pub fn is_matrix(&self) -> bool {
+        unsafe { Rf_isMatrix(self.get()) != 0 }
+    }
+
+    /// Return true if this is a vector list or null.
+    pub fn is_new_list(&self) -> bool {
+        unsafe { Rf_isNewList(self.get()) != 0 }
+    }
+
+    /// Return true if this is a numeric vector 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.
+    pub fn is_primitive(&self) -> bool {
+        unsafe { Rf_isPrimitive(self.get()) != 0 }
+    }
+
+    /// Return true if this is a time series vector (see tsp).
+    pub fn is_ts(&self) -> bool {
+        unsafe { Rf_isTs(self.get()) != 0 }
+    }
+
+    /// Return true if this is a user defined binop.
+    pub fn is_user_binop(&self) -> bool {
+        unsafe { Rf_isUserBinop(self.get()) != 0 }
+    }
+
+    /// Return true if this is a valid string.
+    pub fn is_valid_string(&self) -> bool {
+        unsafe { Rf_isValidString(self.get()) != 0 }
+    }
+
+    /// Return true if this is a valid string.
+    pub fn is_valid_string_f(&self) -> bool {
+        unsafe { Rf_isValidStringF(self.get()) != 0 }
+    }
+
+    /// Return true if this is a vector.
+    pub fn is_vector(&self) -> bool {
+        unsafe { Rf_isVector(self.get()) != 0 }
+    }
+
+    /// Return true if this is an atomic vector.
+    pub fn is_vector_atomic(&self) -> bool {
+        unsafe { Rf_isVectorAtomic(self.get()) != 0 }
+    }
+
+    /// Return true if this is a vector list.
+    pub fn is_vector_list(&self) -> bool {
+        unsafe { Rf_isVectorList(self.get()) != 0 }
+    }
+
+    /// Return true if this is can be made into a vector.
+    pub fn is_vectorizable(&self) -> bool {
+        unsafe { Rf_isVectorizable(self.get()) != 0 }
+    }
+
+    /// Check an external pointer tag.
+    /// This is used to wrap R objects.
+    #[doc(hidden)]
+    pub fn check_external_ptr(&self, expected_tag: &str) -> bool {
+        if self.sexptype() == libR_sys::EXTPTRSXP {
+            let tag = unsafe { self.externalPtrTag() };
+            if tag.as_str() == Some(expected_tag) {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+pub fn find_namespace(name: &str) -> Robj {
+    let name = r!(Symbol(name));
+    single_threaded(|| unsafe { new_borrowed(R_FindNamespace(name.get())) })
+}
diff --git a/extendr-api/src/robj/symbols.rs b/extendr-api/src/robj/symbols.rs
new file mode 100644
index 000000000..bc393c746
--- /dev/null
+++ b/extendr-api/src/robj/symbols.rs
@@ -0,0 +1,208 @@
+use super::*;
+
+/// The "global" environment
+pub fn global_env() -> Robj {
+    unsafe { new_sys(R_GlobalEnv) }
+}
+
+/// An empty environment at the root of the environment tree
+pub fn empty_env() -> Robj {
+    unsafe { new_sys(R_EmptyEnv) }
+}
+
+/// The base environment; formerly R_NilValue
+pub fn base_env() -> Robj {
+    unsafe { new_sys(R_BaseEnv) }
+}
+
+/// The (fake) namespace for base
+pub fn base_namespace() -> Robj {
+    unsafe { new_sys(R_BaseNamespace) }
+}
+
+/// for registered namespaces
+pub fn namespace_registry() -> Robj {
+    unsafe { new_sys(R_NamespaceRegistry) }
+}
+
+/// Current srcref, for debuggers
+pub fn srcref() -> Robj {
+    unsafe { new_sys(R_Srcref) }
+}
+
+/// The nil object
+pub fn nil_value() -> Robj {
+    unsafe { new_sys(R_NilValue) }
+}
+
+/// Unbound marker
+pub fn unbound_value() -> Robj {
+    unsafe { new_sys(R_UnboundValue) }
+}
+
+/// Missing argument marker
+pub fn missing_arg() -> Robj {
+    unsafe { new_sys(R_MissingArg) }
+}
+
+/// "base"
+pub fn base_symbol() -> Robj {
+    unsafe { new_sys(R_BaseSymbol) }
+}
+
+/// "{"
+pub fn brace_symbol() -> Robj {
+    unsafe { new_sys(R_BraceSymbol) }
+}
+
+/// "[["
+pub fn bracket_2_symbol() -> Robj {
+    unsafe { new_sys(R_Bracket2Symbol) }
+}
+
+/// "["
+pub fn bracket_symbol() -> Robj {
+    unsafe { new_sys(R_BracketSymbol) }
+}
+
+/// "class"
+pub fn class_symbol() -> Robj {
+    unsafe { new_sys(R_ClassSymbol) }
+}
+
+/// ".Device"
+pub fn device_symbol() -> Robj {
+    unsafe { new_sys(R_DeviceSymbol) }
+}
+
+/// "dimnames"
+pub fn dimnames_symbol() -> Robj {
+    unsafe { new_sys(R_DimNamesSymbol) }
+}
+
+/// "dim"
+pub fn dim_symbol() -> Robj {
+    unsafe { new_sys(R_DimSymbol) }
+}
+
+/// "$"
+pub fn dollar_symbol() -> Robj {
+    unsafe { new_sys(R_DollarSymbol) }
+}
+
+/// "..."
+pub fn dots_symbol() -> Robj {
+    unsafe { new_sys(R_DotsSymbol) }
+}
+//     pub fn drop_symbol() -> Robj { unsafe { new_sys(R_DropSymbol) }}"drop"
+
+/// "::"
+pub fn double_colon_symbol() -> Robj {
+    unsafe { new_sys(R_DoubleColonSymbol) }
+}
+
+/// ".Last.value"
+pub fn lastvalue_symbol() -> Robj {
+    unsafe { new_sys(R_LastvalueSymbol) }
+}
+/// "levels"
+pub fn levels_symbol() -> Robj {
+    unsafe { new_sys(R_LevelsSymbol) }
+}
+/// "mode"
+pub fn mode_symbol() -> Robj {
+    unsafe { new_sys(R_ModeSymbol) }
+}
+/// "na.rm"
+pub fn na_rm_symbol() -> Robj {
+    unsafe { new_sys(R_NaRmSymbol) }
+}
+/// "name"
+pub fn name_symbol() -> Robj {
+    unsafe { new_sys(R_NameSymbol) }
+}
+/// "names"
+pub fn names_symbol() -> Robj {
+    unsafe { new_sys(R_NamesSymbol) }
+}
+/// _NAMESPACE__."
+pub fn namespace_env_symbol() -> Robj {
+    unsafe { new_sys(R_NamespaceEnvSymbol) }
+}
+/// "package"
+pub fn package_symbol() -> Robj {
+    unsafe { new_sys(R_PackageSymbol) }
+}
+/// "previous"
+pub fn previous_symbol() -> Robj {
+    unsafe { new_sys(R_PreviousSymbol) }
+}
+/// "quote"
+pub fn quote_symbol() -> Robj {
+    unsafe { new_sys(R_QuoteSymbol) }
+}
+/// "row.names"
+pub fn row_names_symbol() -> Robj {
+    unsafe { new_sys(R_RowNamesSymbol) }
+}
+/// ".Random.seed"
+pub fn seeds_symbol() -> Robj {
+    unsafe { new_sys(R_SeedsSymbol) }
+}
+/// "sort.list"
+pub fn sort_list_symbol() -> Robj {
+    unsafe { new_sys(R_SortListSymbol) }
+}
+/// "source"
+pub fn source_symbol() -> Robj {
+    unsafe { new_sys(R_SourceSymbol) }
+}
+/// "spec"
+pub fn spec_symbol() -> Robj {
+    unsafe { new_sys(R_SpecSymbol) }
+}
+/// "tsp"
+pub fn tsp_symbol() -> Robj {
+    unsafe { new_sys(R_TspSymbol) }
+}
+/// ":::"
+pub fn triple_colon_symbol() -> Robj {
+    unsafe { new_sys(R_TripleColonSymbol) }
+}
+/// ".defined"
+pub fn dot_defined() -> Robj {
+    unsafe { new_sys(R_dot_defined) }
+}
+/// ".Method"
+pub fn dot_method() -> Robj {
+    unsafe { new_sys(R_dot_Method) }
+}
+/// "packageName"
+pub fn dot_package_name() -> Robj {
+    unsafe { new_sys(R_dot_packageName) }
+}
+
+/// ".target"
+pub fn dot_target() -> Robj {
+    unsafe { new_sys(R_dot_target) }
+}
+
+/* fix version issues.
+/// ".Generic"
+pub fn dot_Generic() -> Robj { unsafe { new_sys(R_dot_Generic) }}
+*/
+
+/// NA_STRING as a CHARSXP
+pub fn na_string() -> Robj {
+    unsafe { new_sys(R_NaString) }
+}
+
+/// "" as a CHARSXP
+pub fn blank_string() -> Robj {
+    unsafe { new_sys(R_BlankString) }
+}
+
+/// "" as a STRSXP
+pub fn blank_scalar_string() -> Robj {
+    unsafe { new_sys(R_BlankScalarString) }
+}
diff --git a/extendr-api/src/robj_ndarray.rs b/extendr-api/src/robj_ndarray.rs
index 257dc4738..acdd43eec 100644
--- a/extendr-api/src/robj_ndarray.rs
+++ b/extendr-api/src/robj_ndarray.rs
@@ -1,4 +1,6 @@
+#[doc(hidden)]
 use libR_sys::*;
+#[doc(hidden)]
 use ndarray::prelude::*;
 
 use crate::logical::Bool;
diff --git a/extendr-api/src/thread_safety.rs b/extendr-api/src/thread_safety.rs
new file mode 100644
index 000000000..64780f2ff
--- /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 946bf4414..4d339c765 100644
--- a/extendr-api/src/wrapper.rs
+++ b/extendr-api/src/wrapper.rs
@@ -1,24 +1,66 @@
 //! 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::logical::*;
 use crate::robj::*;
+#[doc(hidden)]
 use libR_sys::*;
+#[doc(hidden)]
 use std::ffi::CString;
+use crate::single_threaded;
 
 /// 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!(symbol.is_symbol());
+/// ```
+/// Note that creating a symbol from a string is expensive
+/// and so you may want to cache them.
+///
 #[derive(Debug, PartialEq)]
 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"));
+/// ```
+///
 #[derive(Debug, PartialEq)]
 pub struct Character<'a>(pub &'a str);
 
 /// Wrapper for creating language objects.
+/// Example:
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let _call_to_xyz = r!(Lang("xyz"));
+/// ```
+///
+/// Note: prefer to use the [lang!] macro for this.
 #[derive(Debug, PartialEq)]
 pub struct Lang<'a>(pub &'a str);
 
 /// Wrapper for creating list objects.
+/// Example:
+/// ```
+/// use extendr_api::*;
+/// extendr_engine::start_r();
+/// let mixed_list = r!(List(&[r!(1.), r!("xyz")]));
+/// assert_eq!(mixed_list.len(), 2);
+/// ```
+///
+/// Note: prefer to use the [list!] macro for this.
 #[derive(Debug, PartialEq)]
 pub struct List<'a>(pub &'a [Robj]);
 
@@ -38,46 +80,39 @@ impl<'a> PartialEq<List<'a>> for Robj {
     }
 }
 
-/// Make a list object from an array of Robjs.
 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)
-        }
-    }
-}
-
-/// Convert an integer slice to a logical object.
-impl<'a> From<&'a [Bool]> for Robj {
-    fn from(vals: &[Bool]) -> Self {
-        unsafe {
-            let len = vals.len();
-            let sexp = Rf_allocVector(LGLSXP, len as R_xlen_t);
-            R_PreserveObject(sexp);
-            let ptr = LOGICAL(sexp);
-            let slice = std::slice::from_raw_parts_mut(ptr, len);
-            for (i, &v) in vals.iter().enumerate() {
-                slice[i] = v.0;
-            }
-            Robj::Owned(sexp)
-        }
+        })
     }
 }
 
-/// Convert a string to a symbol.
 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 {
+        single_threaded(|| unsafe {
+            let name = Robj::from(Symbol(val.0));
+            new_owned(Rf_lang1(name.get()))
+        })
     }
 }


Test patch:
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
new file mode 100644
index 000000000..27201c708
--- /dev/null
+++ b/extendr-api/src/robj/tests.rs
@@ -0,0 +1,302 @@
+use super::*;
+
+#[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");
+
+    // Scalars
+    assert_eq!(format!("{:?}", Robj::from(1)), "1");
+    assert_eq!(format!("{:?}", Robj::from(1.)), "1.0");
+    assert_eq!(format!("{:?}", Robj::from("hello")), "[\"hello\"]");
+    let s = "hello".to_string();
+    assert_eq!(format!("{:?}", Robj::from(s)), "[\"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]");
+
+    // Wrappers
+    assert_eq!(format!("{:?}", Robj::from(Symbol("x"))), "Symbol(\"x\")");
+    assert_eq!(
+        format!("{:?}", Robj::from(Character("x"))),
+        "Character(\"x\")"
+    );
+    assert_eq!(
+        format!("{:?}", Robj::from(Lang("x"))),
+        "Lang([Symbol(\"x\")])"
+    );
+
+    // Logical
+    assert_eq!(
+        format!("{:?}", Robj::from([Bool(1), Bool(0)])),
+        "&[Bool(1), Bool(0)]"
+    );
+}
+
+#[test]
+fn test_from_robj() {
+    extendr_engine::start_r();
+    assert_eq!(<bool>::from_robj(&Robj::from(true)), Ok(true));
+    assert_eq!(<u8>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<u16>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<u32>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<u64>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<i8>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<i16>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<i32>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<i64>::from_robj(&Robj::from(1)), Ok(1));
+    assert_eq!(<f32>::from_robj(&Robj::from(1)), Ok(1.));
+    assert_eq!(<f64>::from_robj(&Robj::from(1)), Ok(1.));
+
+    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"));
+
+    // conversion from a vector to a scalar value
+    assert_eq!(
+        <i32>::from_robj(&Robj::from(vec![].as_slice() as &[i32])),
+        Err("Input must be of length 1. Vector of length zero given.")
+    );
+    assert_eq!(
+        <i32>::from_robj(&Robj::from(vec![1].as_slice() as &[i32])),
+        Ok(1)
+    );
+    assert_eq!(
+        <i32>::from_robj(&Robj::from(vec![1, 2].as_slice() as &[i32])),
+        Err("Input must be of length 1. Vector of length >1 given.")
+    );
+
+    use std::collections::HashMap;
+    let list = Robj::eval_string("list(a = 1L, b = 2L)").unwrap();
+    let hmap1 = [("a".into(), 1.into()), ("b".into(), 2.into())]
+        .iter()
+        .cloned()
+        .collect::<HashMap<String, Robj>>();
+    let hmap2 = [("a", 1.into()), ("b", 2.into())]
+        .iter()
+        .cloned()
+        .collect::<HashMap<&str, Robj>>();
+    let hmap_owned = <HashMap<String, Robj>>::from_robj(&list).unwrap();
+    let hmap_borrowed = <HashMap<&str, Robj>>::from_robj(&list).unwrap();
+    assert_eq!(hmap_owned, hmap1);
+    assert_eq!(hmap_borrowed, hmap2);
+
+    assert!(hmap_owned["a"].is_owned());
+    assert_eq!(hmap_owned["a"], Robj::from(1));
+    assert_eq!(hmap_owned["b"], Robj::from(2));
+
+    assert_eq!(hmap_borrowed["a"], Robj::from(1));
+    assert_eq!(hmap_borrowed["b"], Robj::from(2));
+
+    let na_integer = Robj::eval_string("NA_integer_").unwrap();
+    assert!(<i32>::from_robj(&na_integer).is_err());
+    assert_eq!(<Option<i32>>::from_robj(&na_integer), Ok(None));
+    assert_eq!(<Option<i32>>::from_robj(&Robj::from(1)), Ok(Some(1)));
+    assert!(<Option<i32>>::from_robj(&Robj::from([1, 2])).is_err());
+
+    let na_bool = Robj::eval_string("TRUE == NA").unwrap();
+    assert!(<bool>::from_robj(&na_bool).is_err());
+    assert_eq!(<Option<bool>>::from_robj(&na_bool), Ok(None));
+    assert_eq!(<Option<bool>>::from_robj(&Robj::from(true)), Ok(Some(true)));
+    assert!(<Option<bool>>::from_robj(&Robj::from([true, false])).is_err());
+
+    let na_real = Robj::eval_string("NA_real_").unwrap();
+    assert!(<f64>::from_robj(&na_real).is_err());
+    assert_eq!(<Option<f64>>::from_robj(&na_real), Ok(None));
+    assert_eq!(<Option<f64>>::from_robj(&Robj::from(1.)), Ok(Some(1.)));
+    assert!(<Option<f64>>::from_robj(&Robj::from([1., 2.])).is_err());
+
+    let na_string = Robj::eval_string("NA_character_").unwrap();
+    assert!(<&str>::from_robj(&na_string).is_err());
+    assert_eq!(<Option<&str>>::from_robj(&na_string), Ok(None));
+    assert_eq!(<Option<&str>>::from_robj(&Robj::from("1")), Ok(Some("1")));
+    assert!(<Option<&str>>::from_robj(&Robj::from(["1", "2"])).is_err());
+
+    let na_string = Robj::eval_string("NA_character_").unwrap();
+    assert!(<String>::from_robj(&na_string).is_err());
+    assert_eq!(<Option<String>>::from_robj(&na_string), Ok(None));
+    assert_eq!(
+        <Option<String>>::from_robj(&Robj::from("1")),
+        Ok(Some("1".to_string()))
+    );
+    assert!(<Option<String>>::from_robj(&Robj::from(["1", "2"])).is_err());
+}
+#[test]
+fn test_to_robj() {
+    extendr_engine::start_r();
+    assert_eq!(Robj::from(true), Robj::from([Bool::from(true)]));
+    //assert_eq!(Robj::from(1_u8), Robj::from(1));
+    assert_eq!(Robj::from(1_u16), Robj::from(1));
+    assert_eq!(Robj::from(1_u32), Robj::from(1));
+    assert_eq!(Robj::from(1_u64), Robj::from(1));
+    assert_eq!(Robj::from(1_i8), Robj::from(1));
+    assert_eq!(Robj::from(1_i16), Robj::from(1));
+    assert_eq!(Robj::from(1_i32), Robj::from(1));
+    assert_eq!(Robj::from(1_i64), Robj::from(1));
+    assert_eq!(Robj::from(1.0_f32), Robj::from(1.));
+    assert_eq!(Robj::from(1.0_f64), Robj::from(1.));
+
+    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!(Robj::from(Some(1)), Robj::from(1));
+    assert!(!Robj::from(Some(1)).is_na());
+    assert!(Robj::from(<Option<i32>>::None).is_na());
+
+    assert_eq!(Robj::from(Some(true)), Robj::from(true));
+    assert!(!Robj::from(Some(true)).is_na());
+    assert!(Robj::from(<Option<bool>>::None).is_na());
+
+    assert_eq!(Robj::from(Some(1.)), Robj::from(1.));
+    assert!(!Robj::from(Some(1.)).is_na());
+    assert!(Robj::from(<Option<f64>>::None).is_na());
+
+    assert_eq!(Robj::from(Some("xyz")), Robj::from("xyz"));
+    assert!(!Robj::from(Some("xyz")).is_na());
+    assert!(Robj::from(<Option<&str>>::None).is_na());
+}
+
+#[test]
+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 p = Robj::eval_string("1L + 1L")?;
+    assert_eq!(p, Robj::from(2));
+    Ok(())
+}
+
+#[test]
+fn output_iterator_test() -> Result<(), AnyError> {
+    extendr_engine::start_r();
+
+    // Allocation where size is known in advance.
+    let robj = (0..3).collect_robj();
+    assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
+
+    let robj = [0, 1, 2].iter().collect_robj();
+    assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
+
+    let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
+    assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, TRUE]);
+
+    let robj = [TRUE, FALSE, TRUE].iter().collect_robj();
+    assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, TRUE]);
+
+    let robj = (0..3).map(|x| x as f64).collect_robj();
+    assert_eq!(robj.as_real_vector().unwrap(), vec![0., 1., 2.]);
+
+    let robj = [0., 1., 2.].iter().collect_robj();
+    assert_eq!(robj.as_real_vector().unwrap(), vec![0., 1., 2.]);
+
+    let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
+    assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
+
+    let robj = ["0", "1", "2"].iter().collect_robj();
+    assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
+
+    // Fallback allocation where size is not known in advance.
+    let robj = (0..3).filter(|&x| x != 1).collect_robj();
+    assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 2]);
+
+    let robj = (0..3).filter(|&x| x != 1).map(|x| x as f64).collect_robj();
+    assert_eq!(robj.as_real_vector().unwrap(), vec![0., 2.]);
+
+    let robj = (0..3)
+        .filter(|&x| x != 1)
+        .map(|x| format!("{}", x))
+        .collect_robj();
+    assert_eq!(robj.as_str_vector(), Some(vec!["0", "2"]));
+
+    Ok(())
+}
+
+// Test that we can use Iterators as the input to functions.
+// eg.
+// #[extendr]
+// fn fred(a: RealIter, b: RealIter) -> RealIter {
+// }
+#[test]
+fn input_iterator_test() {
+    extendr_engine::start_r();
+
+    let src: &[&str] = &["1", "2", "3"];
+    let robj = Robj::from(src);
+    let iter = <StrIter>::from_robj(&robj).unwrap();
+    assert_eq!(iter.collect::<Vec<_>>(), src);
+
+    let src = &[Robj::from(1), Robj::from(2), Robj::from(3)];
+    let robj = Robj::from(List(src));
+    let iter = <VecIter>::from_robj(&robj).unwrap();
+    assert_eq!(iter.collect::<Vec<_>>(), src);
+
+    let src: &[i32] = &[1, 2, 3];
+    let robj = Robj::from(src);
+    let iter = <IntegerIter>::from_robj(&robj).unwrap();
+    assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
+
+    let src: &[f64] = &[1., 2., 3.];
+    let robj = Robj::from(src);
+    let iter = <RealIter>::from_robj(&robj).unwrap();
+    assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
+
+    /*
+    let src: &[Bool] = &[TRUE, FALSE, TRUE];
+    let robj = Robj::from(src);
+    let iter = <LogicalIter>::from_robj(&robj).unwrap();
+    assert_eq!(iter.cloned().collect::<Vec<_>>(), src);
+    */
+}
diff --git a/tests/extendrtests/NAMESPACE b/tests/extendrtests/NAMESPACE
index 81be91f8f..f804b7780 100644
--- a/tests/extendrtests/NAMESPACE
+++ b/tests/extendrtests/NAMESPACE
@@ -1,5 +1,4 @@
 # Generated by roxygen2: do not edit by hand
 
-export(add_ints)
-export(hello)
+export(hello_world)
 useDynLib(extendrtests, .registration = TRUE)
diff --git a/tests/extendrtests/R/wrappers.R b/tests/extendrtests/R/wrappers.R
index b3fed5f66..a8e0d0cc5 100644
--- a/tests/extendrtests/R/wrappers.R
+++ b/tests/extendrtests/R/wrappers.R
@@ -1,15 +1,8 @@
-#' Wrappers for Rust functions.
+#' Wrappers for Rust test functions.
 #' 
-#' Wrappers for Rust functions.
+#' Wrappers for Rust test functions.
 #' @rdname wrappers
 #' @export
-hello <- function() {
-  .Call("wrap__hello")
+hello_world <- function() {
+  .Call("wrap__hello_world")
 }
-
-#' @rdname wrappers
-#' @param x,y parameters
-#' @export
-add_ints <- function(x, y) {
-  .Call("wrap__add_ints", x, y)
-}
\ No newline at end of file
diff --git a/tests/extendrtests/man/wrappers.Rd b/tests/extendrtests/man/wrappers.Rd
index a2c74a366..4b45ffc3e 100644
--- a/tests/extendrtests/man/wrappers.Rd
+++ b/tests/extendrtests/man/wrappers.Rd
@@ -1,17 +1,11 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/wrappers.R
-\name{hello}
-\alias{hello}
-\alias{add_ints}
-\title{Wrappers for Rust functions.}
+\name{hello_world}
+\alias{hello_world}
+\title{Wrappers for Rust test functions.}
 \usage{
-hello()
-
-add_ints(x, y)
-}
-\arguments{
-\item{x, y}{parameters}
+hello_world()
 }
 \description{
-Wrappers for Rust functions.
+Wrappers for Rust test functions.
 }
diff --git a/tests/extendrtests/src/rust/Cargo.toml b/tests/extendrtests/src/rust/Cargo.toml
index 0ee70d704..4afc483df 100644
--- a/tests/extendrtests/src/rust/Cargo.toml
+++ b/tests/extendrtests/src/rust/Cargo.toml
@@ -16,7 +16,7 @@ crate-type = ["staticlib"]
 extendr-api = "*"
 
 [patch.crates-io]
-extendr-api = { git = "https://github.com/clauswilke/extendr", branch = "reg-forwarding" }
-extendr-engine = { git = "https://github.com/clauswilke/extendr", branch = "reg-forwarding" }
-extendr-macros = { git = "https://github.com/clauswilke/extendr", branch = "reg-forwarding" }
+extendr-api = { git = "https://github.com/extendr/extendr" }
+extendr-engine = { git = "https://github.com/extendr/extendr" }
+extendr-macros = { git = "https://github.com/extendr/extendr" }
 #libR-sys = { git = "https://github.com/extendr/libR-sys" }
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index b840db6c3..be75ee6a3 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -1,19 +1,28 @@
 use extendr_api::*;
 
 #[extendr]
-fn hello() -> &'static str {
-    "hello"
+fn hello_world() -> &'static str {
+    "Hello world!"
 }
 
+// functions to test input/output conversion
 #[extendr]
-fn add_ints(x:i32, y:i32) -> i32 {
-    x + y
-}
+fn double_scalar(x: f64) -> f64 { x }
+#[extendr]
+fn int_scalar(x: i32) -> i32 { x }
+#[extendr]
+fn bool_scalar(x: bool) -> bool { x }
+#[extendr]
+fn char_scalar(x: String) -> String { x }
 
 
 // Macro to generate exports
 extendr_module! {
     mod extendrtests;
-    fn hello;
-    fn add_ints;
+    fn hello_world;
+
+    fn double_scalar;
+    fn int_scalar;
+    fn bool_scalar;
+    fn char_scalar;
 }
diff --git a/tests/extendrtests/tests/testthat/test-type-conversion.R b/tests/extendrtests/tests/testthat/test-type-conversion.R
new file mode 100644
index 000000000..e1287cde1
--- /dev/null
+++ b/tests/extendrtests/tests/testthat/test-type-conversion.R
@@ -0,0 +1,31 @@
+test_that("Conversion of R types to Rust types and vice versa works", {
+  expect_equal(.Call(wrap__double_scalar, .45), .45)
+  expect_equal(.Call(wrap__double_scalar, 15L), 15)
+  expect_error(.Call(wrap__double_scalar, TRUE), "unable to convert")
+  expect_error(.Call(wrap__double_scalar, "abcxyz"), "unable to convert")
+  expect_error(.Call(wrap__double_scalar, NA_real_), "Input must not be NA")
+  expect_error(.Call(wrap__double_scalar, c(.45, .46)), "Input must be of length 1")
+  
+  expect_equal(.Call(wrap__int_scalar, 15L), 15L)
+  expect_equal(.Call(wrap__int_scalar, 4.4), 4L) # is this deliberate? seems dangerous
+  expect_error(.Call(wrap__int_scalar, TRUE), "unable to convert")
+  expect_error(.Call(wrap__int_scalar, "abcxyz"), "unable to convert")
+  expect_error(.Call(wrap__int_scalar, NA_integer_), "Input must not be NA")
+  expect_error(.Call(wrap__int_scalar, 1L:5L), "Input must be of length 1")
+  
+  expect_equal(.Call(wrap__bool_scalar, TRUE), TRUE)
+  expect_equal(.Call(wrap__bool_scalar, FALSE), FALSE)
+  expect_error(.Call(wrap__bool_scalar, .45), "Not a logical object")
+  expect_error(.Call(wrap__bool_scalar, 15L), "Not a logical object")
+  expect_error(.Call(wrap__bool_scalar, "abcxyz"), "Not a logical object")
+  expect_error(.Call(wrap__bool_scalar, NA), "Input must not be NA")
+  expect_error(.Call(wrap__bool_scalar, c(TRUE, FALSE, TRUE)), "Input must be of length 1")
+  
+  expect_equal(.Call(wrap__char_scalar, "abcxyz"), "abcxyz")
+  expect_error(.Call(wrap__char_scalar, .45), "not a string object")
+  expect_error(.Call(wrap__char_scalar, 15L), "not a string object")
+  expect_error(.Call(wrap__char_scalar, TRUE), "not a string object")
+  expect_error(.Call(wrap__char_scalar, NA_character_), "Input must not be NA")
+  expect_error(.Call(wrap__char_scalar, c("hello", "world")), "not a string object") # why this error message and not "Input must be of length 1"?
+})
+
diff --git a/tests/extendrtests/tests/testthat/test-wrappers.R b/tests/extendrtests/tests/testthat/test-wrappers.R
index ccf428381..3a37850dc 100644
--- a/tests/extendrtests/tests/testthat/test-wrappers.R
+++ b/tests/extendrtests/tests/testthat/test-wrappers.R
@@ -1,4 +1,3 @@
-test_that("Call to Rust functions work", {
-  expect_equal(hello(), "hello")
-  expect_equal(add_ints(3L, 5L), 8L)
+test_that("Call to Rust via wrapper functions works", {
+  expect_equal(hello_world(), "Hello world!")
 })