Repository: extendr/extendr
Pull request #190 (2021-03-09 20:59:02)

More wrapper tidying
This PR is an overhaul of the wrapper and conversion process.

Whilst very similar on the top, we will now wrap an Robj in a struct and implement
functions more in the RCpp/C++11 model.

I concede that this a philosophical U-turn, but it makes sense.

Each wrapper will now contain an Robj of the correct type.

Each wrapper implements:

A "from_xxx" impl which constructs the type from components.

A FromRobj impl for legacy parameters.
A TryFrom<Wrapper> impl for new style of parameters.
A From<Robj> impl for returning values.

We hope to migrate from FromRobj to TryFrom in due course.
The TryFrom uses the official extendr Error type to return failure
if the object type is unexpected.

All the as_* impls in Robj can be replaced by try_from versions
we can write a macro to do this.

The names of the is_* impls should match the type.


clauswilke: I have read through the entire PR and marked a few minor issues in the documentation. It all broadly looks good to me, but I'm not sure I fully understand the implications, so somebody else should review also.
andy-thomason: Thanks for taking a look, @clauswilke It is still a WIP, so things may resolve in the end.

I may remove FromRobj completely in favour of TryFrom with proper error handling
(the big new Error enum).

yutannihilation: It seems the test failures are caused by the recent changes on rextendr's side, not by the changes of this pull request. Will investigate.

https://github.com/extendr/extendr/pull/190/checks?check_run_id=2108144242#step:16:394

```
  Error: Error: No root directory found in D:/a/extendr/extendr/rextendr_check/rextendr.Rcheck/tests_i386/testthat or its parent directories. Root criterion: contains a file "DESCRIPTION" with contents matching "^Package: "
  Backtrace:
       x
    1. +-testthat::expect_equal(rust_eval("2 + 2"), 4) test-eval.R:2:2
    2. | \-testthat::quasi_label(enquo(object), label, arg = "object")
    3. |   \-rlang::eval_bare(expr, quo_get_env(quo))
    4. \-rextendr::rust_eval("2 + 2")
    5.   \-rextendr::rust_function(code = code_wrapped, ...)
    6.     \-rextendr::rust_source(code = code, env = env, ...)
    7.       \-rextendr:::make_wrappers(...)
    8.         \-rextendr:::pretty_rel_path(outfile, search_from = path)
    9.           +-fs::path_rel(path, start = rprojroot::find_package_root_file(path = search_from))
   10.           | +-fs::path_abs(path_expand(start))
   11.           | | \-fs::path_norm(path(getwd(), start))
   12.           | \-fs::path_expand(start)
   13.           \-rprojroot::find_package_root_file(path = search_from)
   14.             \-rprojroot::find_root_file(..., criterion = criterion, path = path)
   15.               \-rprojroot::find_root(criterion = criterion, path = path)
```
andy-thomason: Cheers for looking at the build.

This PR should give a lot more options for conversions and tighter type control.

For example.

```
global_env() is now an Evironment

Functions can be built from Pairlist, Language and Environment.

Almost everything is now an Robj wrapper and can return from
an R function.

There are TryFrom conversions for nearly everything.

The Matrix classes have been re-written and also contain an Robj.
```
If you all can give me feedback on what methods you would like, I can add some
more.

Now I'm keen to start writing some real applications to show it off.

clauswilke: @andy-thomason I'd still say a high-priority use case is calling functions from a package and capturing the result. Let's say the following:
```r
data <- tibble::tibble(x = 1:5, y = 6:10)
```
Anything you can do to make this type of use as simple as possible would be great.
clauswilke: Maybe this would argue for a `Function::find_in_namespace()` constructor? So I can write something like:
```rust
let fun = Function::find_in_namespace("tibble", "tibble");
let out = fun.call(pairlist!(x = r!(1..=5), y = r!(6..=10)))
```
yutannihilation: Not sure if this is a scope of this pull request, but I really want to build a `Pairlist` (or `List`) with some elements unnamed programmatically: https://github.com/extendr/extendr/issues/191

IIUC, current implementation is not correct in that it assumes `NA` as the unnamed one, but it's actually `""` (empty string).

``` r
names(pairlist(1, a = 2))
#> [1] ""  "a"
```

<sup>Created on 2021-03-15 by the [reprex package](https://reprex.tidyverse.org) (v1.0.0)</sup>

Look at here. The problem is the line calling `sym_pair()`.

https://github.com/extendr/extendr/blob/660ad8c17952566de5764aaaff18554b1742323e/extendr-api/src/wrapper/pairlist.rs#L29-L37

It attempts to convert `name` into a symbol, but an empty string cannot be a symbol (got an error "attempt to use zero-length variable name"). Should this really be converted to symbols? As `set_names()` works for an empty string, I feel something might be wrong here.

https://github.com/extendr/extendr/blob/dda901ad20cc5d233a9dadd0f6c604d56910f001/extendr-api/src/wrapper/mod.rs#L597-L599
Ilia-Kosenkov: Just to add a little bit of context: names can be set using e.g. `names<-` function and can be (almost) arbitrary strings. For example, several identical names are allowed:
<details>

``` r
l <- list(1, 2, 3, 4, 5, 6)
names(l) <- c("", "a", "", "a", "", "a")
l
#> [[1]]
#> [1] 1
#> 
#> $a
#> [1] 2
#> 
#> [[3]]
#> [1] 3
#> 
#> $a
#> [1] 4
#> 
#> [[5]]
#> [1] 5
#> 
#> $a
#> [1] 6

l <- list(1, 2)
names(l) <- c("+", "...")
l[["+"]]
#> [1] 1
l[["..."]]
#> [1] 2
```

<sup>Created on 2021-03-15 by the [reprex package](https://reprex.tidyverse.org) (v1.0.0)</sup>


</details>
clauswilke: Tests mostly work now. Not clear to me whether the fail for R devel is a fluke or a systematic problem.
andy-thomason: With from_pairs(...) I've arranged so that na_str() should not set the tag. I should also arrange so that an empty string does
the same. List uses empty strings to signal "no tag".

```
> l <- list(a=1, 2)
> .Internal(inspect(l))
@55f60dd6ace8 19 VECSXP g0c2 [REF(1),ATT] (len=2, tl=0)
  @55f60f15f6e0 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 1
  @55f60f15f6a8 14 REALSXP g0c1 [REF(3)] (len=1, tl=0) 2
ATTRIB:
  @55f60f1740d0 02 LISTSXP g0c0 [REF(1)] 
    TAG: @55f60d226990 01 SYMSXP g0c0 [MARK,REF(5645),LCK,gp=0x4000] "names" (has value)
    @55f60dd6ad28 16 STRSXP g0c2 [REF(1)] (len=2, tl=0)
      @55f60d507140 09 CHARSXP g0c1 [MARK,REF(6),gp=0x61] [ASCII] [cached] "a"
      @55f60d229058 09 CHARSXP g0c1 [MARK,REF(8),gp=0x60] [ASCII] [cached] ""
```

```
> p <- pairlist(a=1, 2)
> .Internal(inspect(p))
@55f60f25a9e8 02 LISTSXP g0c0 [REF(1)] 
  TAG: @55f60d5e2b60 01 SYMSXP g0c0 [MARK,REF(23)] "a"
  @55f60f15f600 14 REALSXP g0c1 [REF(5)] (len=1, tl=0) 1
  @55f60f15f5c8 14 REALSXP g0c1 [REF(5)] (len=1, tl=0) 2
```

I'm trying to make List and Pairlist look much the same. Both take (string-ish, object-ish) tuples and I'll fix the list! and pairlist! macros to generate these more efficiently when we are happy.
yutannihilation: @andy-thomason 
> I've arranged so that na_str() should not set the tag.

If this works, I'm fine. But I suspect this condition is never met

``` rust
     if !name.is_na() { 
```

because a symbol cannot be missing.

``` r
rextendr::rust_function(r"(
fn f() -> bool {
  let x = Symbol::from_str(na_str());
  x.is_na()
}
)", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr", branch = "more-wrapper-tidying"))
)
#> build directory: /tmp/RtmpC8nsIa/fileb52b7bccd66e
#> ✓ Writting wrappers to /tmp/RtmpC8nsIa/fileb52b7bccd66e/target/extendr_wrappers.R.

f()
#> [1] FALSE
```

<sup>Created on 2021-03-16 by the [reprex package](https://reprex.tidyverse.org) (v1.0.0)</sup>

I guess that's why we are seeing the element named "NA", instead of an unnamed one.

<details>

See #191 for more details

``` r
rextendr::rust_function(r"(
fn call_pairlist(f: Robj) -> Robj {
  let names_and_values = vec![("a", r!(1)), ("b", r!(2)), (na_str(), r!(3))];
  let pairlist = Pairlist{ names_and_values };
  f.call(pairlist.into_robj()).unwrap()
}
)", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr"))
)
#> build directory: /tmp/Rtmp3Tjm0Q/file1022ad72ffb210
#> Writting wrappers to:
#> /tmp/Rtmp3Tjm0Q/file1022ad72ffb210/target/extendr_wrappers.R

call_pairlist(function(...) list(...))
#> $a
#> [1] 1
#> 
#> $b
#> [1] 2
#> 
#> $`NA`
#> [1] 3
```

</details>
andy-thomason: We do have unbound_value() for symbols, but the pairlist usually nulls the
Tag.

The logic is handled by the SymPair trait which is designed to pass through
symbols (they are
expensive to create).

We could make "", na_str() and unbound_value() all leave the tag unset.

I'll get working on a replacement for pairlist! and list! at the earliest.

I have only the atomic vectors Int, Real and Logical to convert to the
standardised wrappers.


On Tue, Mar 16, 2021 at 1:10 AM Hiroaki Yutani ***@***.***>
wrote:

> @andy-thomason <https://github.com/andy-thomason>
>
> I've arranged so that na_str() should not set the tag.
>
> If this works, I'm fine. But I suspect this condition is never met
>
>      if !name.is_na() {
>
> because a symbol cannot be missing.
>
> rextendr::rust_function(r"(
> fn f() -> bool {
>   let x = Symbol::from_str(na_str());
>   x.is_na()
> }
> )", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr", branch = "more-wrapper-tidying"))
>
> )
> #> build directory: /tmp/RtmpC8nsIa/fileb52b7bccd66e
> #> ✓ Writting wrappers to /tmp/RtmpC8nsIa/fileb52b7bccd66e/target/extendr_wrappers.R.
>
>
>
> f()
> #> [1] FALSE
>
> Created on 2021-03-16 by the reprex package <https://reprex.tidyverse.org>
> (v1.0.0)
>
> I guess that's why we are seeing the element named "NA", instead of an
> unnamed one.
>
> See #191 <https://github.com/extendr/extendr/issues/191> for more details
>
> rextendr::rust_function(r"(
> fn call_pairlist(f: Robj) -> Robj {
>   let names_and_values = vec![("a", r!(1)), ("b", r!(2)), (na_str(), r!(3))];
>   let pairlist = Pairlist{ names_and_values };
>   f.call(pairlist.into_robj()).unwrap()
> }
> )", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr"))
>
> )
> #> build directory: /tmp/Rtmp3Tjm0Q/file1022ad72ffb210
> #> Writting wrappers to:
> #> /tmp/Rtmp3Tjm0Q/file1022ad72ffb210/target/extendr_wrappers.R
>
>
>
> call_pairlist(function(...) list(...))
> #> $a
> #> [1] 1
> #>
> #> $b
> #> [1] 2
> #>
> #> $`NA`
> #> [1] 3
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/extendr/extendr/pull/190#issuecomment-799867546>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAL36XA7C2KHSY4TDQHQYHDTD2VW5ANCNFSM4Y4O3FDA>
> .
>

andy-thomason: Under the new system call should take a Pairlist argument as well to make
it clear.

On Tue, Mar 16, 2021 at 12:25 PM Andy Thomason ***@***.***>
wrote:

> We do have unbound_value() for symbols, but the pairlist usually nulls the
> Tag.
>
> The logic is handled by the SymPair trait which is designed to pass
> through symbols (they are
> expensive to create).
>
> We could make "", na_str() and unbound_value() all leave the tag unset.
>
> I'll get working on a replacement for pairlist! and list! at the earliest.
>
> I have only the atomic vectors Int, Real and Logical to convert to the
> standardised wrappers.
>
>
> On Tue, Mar 16, 2021 at 1:10 AM Hiroaki Yutani ***@***.***>
> wrote:
>
>> @andy-thomason <https://github.com/andy-thomason>
>>
>> I've arranged so that na_str() should not set the tag.
>>
>> If this works, I'm fine. But I suspect this condition is never met
>>
>>      if !name.is_na() {
>>
>> because a symbol cannot be missing.
>>
>> rextendr::rust_function(r"(
>> fn f() -> bool {
>>   let x = Symbol::from_str(na_str());
>>   x.is_na()
>> }
>> )", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr", branch = "more-wrapper-tidying"))
>>
>> )
>> #> build directory: /tmp/RtmpC8nsIa/fileb52b7bccd66e
>> #> ✓ Writting wrappers to /tmp/RtmpC8nsIa/fileb52b7bccd66e/target/extendr_wrappers.R.
>>
>>
>>
>> f()
>> #> [1] FALSE
>>
>> Created on 2021-03-16 by the reprex package
>> <https://reprex.tidyverse.org> (v1.0.0)
>>
>> I guess that's why we are seeing the element named "NA", instead of an
>> unnamed one.
>>
>> See #191 <https://github.com/extendr/extendr/issues/191> for more details
>>
>> rextendr::rust_function(r"(
>> fn call_pairlist(f: Robj) -> Robj {
>>   let names_and_values = vec![("a", r!(1)), ("b", r!(2)), (na_str(), r!(3))];
>>   let pairlist = Pairlist{ names_and_values };
>>   f.call(pairlist.into_robj()).unwrap()
>> }
>> )", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr"))
>>
>> )
>> #> build directory: /tmp/Rtmp3Tjm0Q/file1022ad72ffb210
>> #> Writting wrappers to:
>> #> /tmp/Rtmp3Tjm0Q/file1022ad72ffb210/target/extendr_wrappers.R
>>
>>
>>
>> call_pairlist(function(...) list(...))
>> #> $a
>> #> [1] 1
>> #>
>> #> $b
>> #> [1] 2
>> #>
>> #> $`NA`
>> #> [1] 3
>>
>> —
>> You are receiving this because you were mentioned.
>> Reply to this email directly, view it on GitHub
>> <https://github.com/extendr/extendr/pull/190#issuecomment-799867546>, or
>> unsubscribe
>> <https://github.com/notifications/unsubscribe-auth/AAL36XA7C2KHSY4TDQHQYHDTD2VW5ANCNFSM4Y4O3FDA>
>> .
>>
>

yutannihilation: Thanks, confirmed it works now! I'm still struggling to see what's the supposed way of handling `""` and `na_str()` (as I don't know much about the internals around here, sorry), but I'm fine with the current implementation for my usage.

``` r
rextendr::rust_function(r"(
fn call_pairlist(f: Robj) -> Robj {
  let pairs = vec![("a", r!(1)), ("b", r!(2)), (na_str(), r!(3))];
  let pairlist = Pairlist::from_pairs(pairs);
  f.call(pairlist.into_robj()).unwrap()
}
)", patch.crates_io = list(`extendr-api` = list(git = "https://github.com/extendr/extendr", branch = "more-wrapper-tidying"))
)
#> build directory: /tmp/RtmpiG5eGz/file1a1041cacbf6b
#> ✓ Writing wrappers to /tmp/RtmpiG5eGz/file1a1041cacbf6b/target/extendr_wrappers.R.

call_pairlist(function(...) list(...))
#> $a
#> [1] 1
#> 
#> $b
#> [1] 2
#> 
#> [[3]]
#> [1] 3
```

<sup>Created on 2021-03-17 by the [reprex package](https://reprex.tidyverse.org) (v1.0.0)</sup>

Patch:
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index a8f244121..e9561e3bb 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -25,12 +25,11 @@ pub fn unwrap_or_throw<T>(r: std::result::Result<T, &'static str>) -> T {
 
 #[derive(Debug, PartialEq)]
 pub enum Error {
-    Panic {},
-    NotFound,
-    NotAVectorType,
-    EvalError { code: Robj, error: i32 },
-    ParseError { code: String, status: u32 },
-    NamesLengthMismatch,
+    Panic(Robj),
+    NotFound(Robj),
+    EvalError(Robj),
+    ParseError(Robj),
+    NamesLengthMismatch(Robj),
 
     ExpectedNull(Robj),
     ExpectedSymbol(Robj),
@@ -56,17 +55,91 @@ pub enum Error {
     ExpectedWeakRef(Robj),
     ExpectedRaw(Robj),
     ExpectedS4(Robj),
+    ExpectedPrimitive(Robj),
+
+    ExpectedScalar(Robj),
+    ExpectedVector(Robj),
+    ExpectedMatrix(Robj),
+    ExpectedMatrix3D(Robj),
+    ExpectedNumeric(Robj),
+    OutOfRange(Robj),
+    MustNotBeNA(Robj),
+    ExpectedNonZeroLength(Robj),
+    TypeMismatch(Robj),
+    NamespaceNotFound(Robj),
 
     Other(String),
 }
 
-pub type Result<T> = std::result::Result<T, Error>;
-
 impl std::fmt::Display for Error {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:?}", self)
+        match self {
+            Error::Panic(robj) => write!(f, "Panic detected {:?}.", robj),
+            Error::NotFound(robj) => write!(f, "Not found. {:?}", robj),
+            Error::EvalError(robj) => write!(f, "Evalualtion error in {:?}.", robj),
+            Error::ParseError(code) => write!(f, "Parse error in {:?}.", code),
+            Error::NamesLengthMismatch(robj) => {
+                write!(f, "Length of names does not match vector. {:?}", robj)
+            }
+
+            Error::ExpectedNull(robj) => write!(f, "Expected Null got {:?}", robj.rtype()),
+            Error::ExpectedSymbol(robj) => write!(f, "Expected Symbol got {:?}", robj.rtype()),
+            Error::ExpectedPairlist(robj) => write!(f, "Expected Pairlist got {:?}", robj.rtype()),
+            Error::ExpectedFunction(robj) => write!(f, "Expected Function got {:?}", robj.rtype()),
+            Error::ExpectedEnviroment(robj) => {
+                write!(f, "Expected Enviroment got {:?}", robj.rtype())
+            }
+            Error::ExpectedPromise(robj) => write!(f, "Expected Promise got {:?}", robj.rtype()),
+            Error::ExpectedLanguage(robj) => write!(f, "Expected Language got {:?}", robj.rtype()),
+            Error::ExpectedSpecial(robj) => write!(f, "Expected Special got {:?}", robj.rtype()),
+            Error::ExpectedBuiltin(robj) => write!(f, "Expected Builtin got {:?}", robj.rtype()),
+            Error::ExpectedCharacter(robj) => {
+                write!(f, "Expected Character got {:?}", robj.rtype())
+            }
+            Error::ExpectedLogical(robj) => write!(f, "Expected Logical got {:?}", robj.rtype()),
+            Error::ExpectedInteger(robj) => write!(f, "Expected Integer got {:?}", robj.rtype()),
+            Error::ExpectedReal(robj) => write!(f, "Expected Real got {:?}", robj.rtype()),
+            Error::ExpectedComplex(robj) => write!(f, "Expected Complex got {:?}", robj.rtype()),
+            Error::ExpectedString(robj) => write!(f, "Expected String got {:?}", robj.rtype()),
+            Error::ExpectedDot(robj) => write!(f, "Expected Dot got {:?}", robj.rtype()),
+            Error::ExpectedAny(robj) => write!(f, "Expected Any got {:?}", robj.rtype()),
+            Error::ExpectedList(robj) => write!(f, "Expected List got {:?}", robj.rtype()),
+            Error::ExpectedExpression(robj) => {
+                write!(f, "Expected Expression got {:?}", robj.rtype())
+            }
+            Error::ExpectedBytecode(robj) => write!(f, "Expected Bytecode got {:?}", robj.rtype()),
+            Error::ExpectedExternalPtr(robj) => {
+                write!(f, "Expected ExternalPtr got {:?}", robj.rtype())
+            }
+            Error::ExpectedWeakRef(robj) => write!(f, "Expected WeakRef got {:?}", robj.rtype()),
+            Error::ExpectedRaw(robj) => write!(f, "Expected Raw got {:?}", robj.rtype()),
+            Error::ExpectedS4(robj) => write!(f, "Expected S4 got {:?}", robj.rtype()),
+            Error::ExpectedPrimitive(robj) => {
+                write!(f, "Expected Primitive got {:?}", robj.rtype())
+            }
+
+            Error::ExpectedScalar(robj) => write!(f, "Expected Scalar, got {:?}", robj.rtype()),
+            Error::ExpectedVector(robj) => write!(f, "Expected Vector, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix(robj) => write!(f, "Expected Matrix, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix3D(robj) => write!(f, "Expected Matrix3D, got {:?}", robj.rtype()),
+            Error::ExpectedNumeric(robj) => write!(f, "Expected Numeric, got {:?}", robj.rtype()),
+            Error::OutOfRange(_robj) => write!(f, "Out of range."),
+            Error::MustNotBeNA(_robj) => write!(f, "Must not be NA."),
+            Error::ExpectedNonZeroLength(_robj) => write!(f, "Expected non zero length"),
+            Error::TypeMismatch(_robj) => write!(f, "Type mismatch"),
+
+            Error::NamespaceNotFound(robj) => write!(f, "Namespace {:?} not found", robj),
+            Error::Other(str) => write!(f, "{}", str),
+        }
     }
 }
+pub type Result<T> = std::result::Result<T, Error>;
+
+// impl std::fmt::Display for Error {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         write!(f, "{:?}", self)
+//     }
+// }
 
 impl std::error::Error for Error {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
diff --git a/extendr-api/src/functions.rs b/extendr-api/src/functions.rs
index 8d7c81709..b0b5f7504 100644
--- a/extendr-api/src/functions.rs
+++ b/extendr-api/src/functions.rs
@@ -1,44 +1,5 @@
 use crate::*;
 
-/// Create a new, empty environment parented on global_env()
-///
-/// Use the Env{} wrapper for more detail.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let env = new_env();
-///     assert_eq!(env.len(), 0);
-/// }
-/// ```
-pub fn new_env() -> Robj {
-    // 14 is a reasonable default.
-    new_env_with_capacity(14)
-}
-
-/// Create a new, empty environment parented on global_env()
-/// with a reserved size.
-///
-/// This function will guess the hash table size if required.
-/// Use the Env{} wrapper for more detail.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let env = new_env_with_capacity(5);
-///     env.set_local(sym!(a), 1);
-///     env.set_local(sym!(b), 2);
-///     assert_eq!(env.len(), 2);
-/// }
-/// ```
-pub fn new_env_with_capacity(capacity: usize) -> Robj {
-    if capacity <= 5 {
-        // Unhashed envirnment
-        call!("new.env", FALSE, global_env(), 0).unwrap()
-    } else {
-        // Hashed environment for larger hashmaps.
-        call!("new.env", TRUE, global_env(), capacity as i32 * 2 + 1).unwrap()
-    }
-}
-
 /// Get a global variable from global_env() and ancestors.
 /// If the result is a promise, evaulate the promise.
 ///
@@ -51,10 +12,8 @@ pub fn new_env_with_capacity(capacity: usize) -> Robj {
 /// }
 /// ```
 pub fn global_var<K: Into<Robj>>(key: K) -> Result<Robj> {
-    global_env()
-        .find_var(key)
-        .ok_or_else(|| Error::NotFound)
-        .and_then(|v| v.eval_promise())
+    let key = key.into();
+    Ok(global_env().find_var(key)?.eval_promise()?)
 }
 
 /// Get a local variable from current_env() and ancestors.
@@ -73,21 +32,20 @@ pub fn global_var<K: Into<Robj>>(key: K) -> Result<Robj> {
 /// }
 /// ```
 pub fn local_var<K: Into<Robj>>(key: K) -> Result<Robj> {
-    current_env()
-        .find_var(key)
-        .ok_or_else(|| Error::NotFound)
-        .and_then(|v| v.eval_promise())
+    let key = key.into();
+    Ok(current_env().find_var(key)?.eval_promise()?)
 }
 
 /// Get a global function from global_env() and ancestors.
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let ls = global_function(sym!(ls)).ok_or("ls failed")?;
+///     let ls = global_function(sym!(ls))?;
 ///     assert_eq!(ls.is_function(), true);
 /// }
 /// ```
-pub fn global_function<K: Into<Robj>>(key: K) -> Option<Robj> {
+pub fn global_function<K: Into<Robj>>(key: K) -> Result<Robj> {
+    let key = key.into();
     global_env().find_function(key)
 }
 
@@ -97,16 +55,17 @@ pub fn global_function<K: Into<Robj>>(key: K) -> Option<Robj> {
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///    assert_eq!(find_namespace("base").is_some(), true);
-///    assert_eq!(find_namespace("stats").is_some(), true);
+///    assert_eq!(find_namespace("base").is_ok(), true);
+///    assert_eq!(find_namespace("stats").is_ok(), true);
 /// }
 /// ```
-pub fn find_namespace<K: Into<Robj>>(key: K) -> Option<Robj> {
-    let res = single_threaded(|| call!(".getNamespace", key.into()));
+pub fn find_namespace<K: Into<Robj>>(key: K) -> Result<Environment> {
+    let key = key.into();
+    let res = single_threaded(|| call!(".getNamespace", key.clone()));
     if let Ok(res) = res {
-        Some(res)
+        Ok(res.try_into()?)
     } else {
-        None
+        Err(Error::NamespaceNotFound(key))
     }
 }
 
@@ -118,8 +77,8 @@ pub fn find_namespace<K: Into<Robj>>(key: K) -> Option<Robj> {
 ///    assert_eq!(current_env(), base_env());
 /// }
 /// ```
-pub fn current_env() -> Robj {
-    unsafe { new_owned(R_GetCurrentEnv()) }
+pub fn current_env() -> Environment {
+    unsafe { new_owned(R_GetCurrentEnv()).try_into().unwrap() }
 }
 
 /// The "global" environment
@@ -128,16 +87,16 @@ pub fn current_env() -> Robj {
 /// use extendr_api::prelude::*;
 /// test! {
 ///     global_env().set_local(sym!(x), "hello");
-///     assert_eq!(global_env().local(sym!(x)), Some(r!("hello")));
+///     assert_eq!(global_env().local(sym!(x)), Ok(r!("hello")));
 /// }
 /// ```
-pub fn global_env() -> Robj {
-    unsafe { new_sys(R_GlobalEnv) }
+pub fn global_env() -> Environment {
+    unsafe { new_sys(R_GlobalEnv).try_into().unwrap() }
 }
 
 /// An empty environment at the root of the environment tree
-pub fn empty_env() -> Robj {
-    unsafe { new_sys(R_EmptyEnv) }
+pub fn empty_env() -> Environment {
+    unsafe { new_sys(R_EmptyEnv).try_into().unwrap() }
 }
 
 /// The base environment; formerly R_NilValue
@@ -146,11 +105,11 @@ pub fn empty_env() -> Robj {
 /// use extendr_api::prelude::*;
 /// test! {
 ///     global_env().set_local(sym!(x), "hello");
-///     assert_eq!(base_env().local(sym!(+)), Some(r!(Primitive("+"))));
+///     assert_eq!(base_env().local(sym!(+)), Ok(r!(Primitive::from_str("+"))));
 /// }
 /// ```
-pub fn base_env() -> Robj {
-    unsafe { new_sys(R_BaseEnv) }
+pub fn base_env() -> Environment {
+    unsafe { new_sys(R_BaseEnv).try_into().unwrap() }
 }
 
 /// The namespace for base.
@@ -161,8 +120,8 @@ pub fn base_env() -> Robj {
 ///    assert_eq!(base_namespace().parent().ok_or("no parent")?, global_env());
 /// }
 /// ```
-pub fn base_namespace() -> Robj {
-    unsafe { new_sys(R_BaseNamespace) }
+pub fn base_namespace() -> Environment {
+    unsafe { new_sys(R_BaseNamespace).try_into().unwrap() }
 }
 
 /// For registered namespaces.
@@ -173,8 +132,8 @@ pub fn base_namespace() -> Robj {
 ///    assert_eq!(namespace_registry().is_environment(), true);
 /// }
 /// ```
-pub fn namespace_registry() -> Robj {
-    unsafe { new_sys(R_NamespaceRegistry) }
+pub fn namespace_registry() -> Environment {
+    unsafe { new_sys(R_NamespaceRegistry).try_into().unwrap() }
 }
 
 /// Current srcref, for debuggers
@@ -187,158 +146,6 @@ 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) }}
@@ -378,7 +185,7 @@ pub fn na_str() -> &'static str {
 /// use extendr_api::prelude::*;
 /// test! {
 ///    let expr = parse("1 + 2").unwrap();
-///    assert!(expr.is_expr());
+///    assert!(expr.is_expression());
 /// }
 /// ```
 pub fn parse(code: &str) -> Result<Robj> {
@@ -390,10 +197,7 @@ pub fn parse(code: &str) -> Result<Robj> {
         let parsed = new_owned(R_ParseVector(codeobj.get(), -1, status_ptr, R_NilValue));
         match status {
             1 => Ok(parsed),
-            _ => Err(Error::ParseError {
-                code: code.into(),
-                status,
-            }),
+            _ => Err(Error::ParseError(code.into())),
         }
     })
 }
@@ -410,11 +214,39 @@ pub fn eval_string(code: &str) -> Result<Robj> {
     single_threaded(|| {
         let expr = parse(code)?;
         let mut res = Robj::from(());
-        if let Some(iter) = expr.as_list_iter() {
-            for lang in iter {
+        if let Some(expr) = expr.as_expression() {
+            for lang in expr.values() {
                 res = lang.eval()?
             }
         }
         Ok(res)
     })
 }
+
+/// Find a function or primitive that may be in a namespace.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///    assert!(find_namespaced_function("+").is_ok());
+///    assert!(find_namespaced_function("ls").is_ok());
+///    assert!(find_namespaced_function("base::ls").is_ok());
+///    assert!(find_namespaced_function("ls")?.is_language());
+///    assert!(!find_namespaced_function("basex::ls").is_ok());
+/// }
+/// ```
+pub fn find_namespaced_function(name: &str) -> Result<Language> {
+    let mut iter = name.split("::");
+    match (iter.next(), iter.next(), iter.next()) {
+        (Some(key), None, None) => {
+            let gf = global_function(Symbol::from_str(key))?;
+            Ok(Language::from_values(&[gf]))
+        }
+        (Some(ns), Some(key), None) => {
+            let namespace = find_namespace(ns)?;
+            Ok(Language::from_values(&[
+                namespace.local(Symbol::from_str(key))?
+            ]))
+        }
+        _ => Err(Error::NotFound(r!(name))),
+    }
+}
diff --git a/extendr-api/src/iter.rs b/extendr-api/src/iter.rs
index e028582a0..77965c8ef 100644
--- a/extendr-api/src/iter.rs
+++ b/extendr-api/src/iter.rs
@@ -1,64 +1,6 @@
 use crate::*;
 
-/// Iterator over the objects in a VECSXP, EXPRSXP or WEAKREFSXP.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let my_list = list!(a = 1, b = 2);
-///     let mut total = 0;
-///     for robj in my_list.as_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 {
-    vector: Robj,
-    i: usize,
-    len: usize,
-}
-
-impl ListIter {
-    // A new, empty list iterator.
-    pub fn new() -> Self {
-        ListIter {
-            vector: ().into(),
-            i: 0,
-            len: 0,
-        }
-    }
-}
-
-impl Iterator for ListIter {
-    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(unsafe { new_owned(VECTOR_ELT(self.vector.get(), i as isize)) })
-        }
-    }
-
-    fn nth(&mut self, n: usize) -> Option<Self::Item> {
-        self.i += n;
-        self.next()
-    }
-}
+use wrapper::symbol::levels_symbol;
 
 /// Generalised iterator of numbers and logical. See Int, Real and Logical.
 pub struct SliceIter<T> {
@@ -116,6 +58,12 @@ impl<T: Copy> Iterator for SliceIter<T> {
     }
 }
 
+impl<T: Copy> ExactSizeIterator for SliceIter<T> {
+    fn len(&self) -> usize {
+        self.len - self.i
+    }
+}
+
 /// Iterator over name-value pairs in lists.
 pub type NamedListIter = std::iter::Zip<StrIter, ListIter>;
 
@@ -149,87 +97,6 @@ pub type Real = SliceIter<f64>;
 /// ```
 pub type Logical = SliceIter<Bool>;
 
-#[derive(Clone)]
-pub struct PairlistIter {
-    root_obj: Robj,
-    list_elem: SEXP,
-}
-
-impl PairlistIter {
-    /// Make an empty list iterator.
-    pub fn new() -> Self {
-        unsafe {
-            Self {
-                root_obj: ().into(),
-                list_elem: R_NilValue,
-            }
-        }
-    }
-}
-
-impl Iterator for PairlistIter {
-    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_owned(CAR(sexp)))
-            }
-        }
-    }
-}
-
-#[derive(Clone)]
-/// Iterator over pairlist tag names.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
-///     let tags : Vec<_> = robj.as_pairlist_tag_iter().unwrap().collect();
-///     assert_eq!(tags, vec!["a", "b", na_str()]);
-/// }
-/// ```
-pub struct PairlistTagIter {
-    root_obj: Robj,
-    list_elem: SEXP,
-}
-
-impl PairlistTagIter {
-    /// Make an empty list iterator.
-    pub fn new() -> Self {
-        unsafe {
-            Self {
-                root_obj: ().into(),
-                list_elem: R_NilValue,
-            }
-        }
-    }
-}
-
-impl Iterator for PairlistTagIter {
-    type Item = &'static str;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        unsafe {
-            let sexp = self.list_elem;
-            if sexp == R_NilValue {
-                None
-            } else {
-                self.list_elem = CDR(sexp);
-                if let Some(symbol) = new_owned(TAG(sexp)).as_symbol() {
-                    Some(std::mem::transmute(symbol.0))
-                } else {
-                    Some(na_str())
-                }
-            }
-        }
-    }
-}
-
 /// Iterator over strings or string factors.
 ///
 /// ```
@@ -265,6 +132,17 @@ impl StrIter {
             }
         }
     }
+
+    pub fn na_iter(len: usize) -> StrIter {
+        unsafe {
+            Self {
+                vector: ().into(),
+                i: 0,
+                len: len,
+                levels: R_NilValue,
+            }
+        }
+    }
 }
 
 // Get a string reference from a CHARSXP
@@ -306,6 +184,8 @@ impl Iterator for StrIter {
             } else if TYPEOF(vector) as u32 == INTSXP && TYPEOF(self.levels) as u32 == STRSXP {
                 let j = *(INTEGER(vector).offset(i as isize));
                 Some(str_from_strsxp(self.levels, j as isize - 1))
+            } else if TYPEOF(vector) as u32 == NILSXP {
+                Some(na_str())
             } else {
                 return None;
             }
@@ -318,6 +198,12 @@ impl Iterator for StrIter {
     }
 }
 
+impl ExactSizeIterator for StrIter {
+    fn len(&self) -> usize {
+        self.len - self.i
+    }
+}
+
 macro_rules! impl_iter_debug {
     ($name: ty) => {
         impl std::fmt::Debug for $name {
@@ -334,146 +220,12 @@ macro_rules! impl_iter_debug {
     };
 }
 
-/// Iterator over the names and values of an environment
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
-///     let env = Env{parent: global_env(), names_and_values};
-///     let robj = r!(env);
-///     let names_and_values = robj.as_env_iter().unwrap().collect::<Vec<_>>();
-///     assert_eq!(names_and_values.len(), 100);
-///
-///     let small_env = new_env_with_capacity(1);
-///     small_env.set_local(sym!(x), 1);
-///     let names_and_values = small_env.as_env_iter().unwrap().collect::<Vec<_>>();
-///     assert_eq!(names_and_values, vec![("x", r!(1))]);
-///
-///     let large_env = new_env_with_capacity(1000);
-///     large_env.set_local(sym!(x), 1);
-///     let names_and_values = large_env.as_env_iter().unwrap().collect::<Vec<_>>();
-///     assert_eq!(names_and_values, vec![("x", r!(1))]);
-/// }
-///
-/// ```
-#[derive(Clone)]
-pub struct EnvIter {
-    hash_table: ListIter,
-    pairlist: PairlistIter,
-    pairlisttags: PairlistTagIter,
-}
-
-impl Iterator for EnvIter {
-    type Item = (&'static str, Robj);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        loop {
-            // Environments are a hash table (list) or pair lists (pairlist)
-            // Get the first available value from the pair list.
-            loop {
-                match (self.pairlisttags.next(), self.pairlist.next()) {
-                    (Some(key), Some(value)) => {
-                        // if the key and value are valid, return a pair.
-                        if !key.is_na() && !value.is_unbound_value() {
-                            println!("value: {:?}", (&key, &value));
-                            return Some((key, value));
-                        }
-                    }
-                    // if the key and value are invalid, move on to the hash table.
-                    _ => break,
-                }
-                // continue pair list loop.
-            }
-
-            // Get the first pairlist from the hash table.
-            loop {
-                if let Some(obj) = self.hash_table.next() {
-                    if !obj.is_null() && obj.is_pairlist() {
-                        self.pairlisttags = obj.as_pairlist_tag_iter().unwrap();
-                        self.pairlist = obj.as_pairlist_iter().unwrap();
-                        break;
-                    }
-                // continue hash table loop.
-                } else {
-                    // The hash table is empty, end of iteration.
-                    return None;
-                }
-            }
-        }
-    }
-}
-
 impl_iter_debug!(ListIter);
 impl_iter_debug!(PairlistIter);
-impl_iter_debug!(PairlistTagIter);
 impl_iter_debug!(StrIter);
 impl_iter_debug!(EnvIter);
 
 impl Robj {
-    /// Get an iterator over a pairlist objects.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
-    ///     let objects : Vec<_> = robj.as_pairlist_iter().unwrap().collect();
-    ///     assert_eq!(objects, vec![r!(1.0), r!(2.0), r!(3.0)]);
-    /// }
-    /// ```
-    pub fn as_pairlist_iter(&self) -> Option<PairlistIter> {
-        match self.sexptype() {
-            LISTSXP | LANGSXP | DOTSXP => unsafe {
-                Some(PairlistIter {
-                    root_obj: self.into(),
-                    list_elem: self.get(),
-                })
-            },
-            _ => None,
-        }
-    }
-
-    /// Get an iterator over pairlist tags.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let mut robj = R!(pairlist(a = 1, b = 2, 3)).unwrap();
-    ///     // let mut robj = pairlist!(a = 1, b = 2, 3);
-    ///     let tags : Vec<_> = robj.as_pairlist_tag_iter().unwrap().collect();
-    ///     assert_eq!(tags, vec!["a", "b", na_str()]);
-    /// }
-    /// ```
-    pub fn as_pairlist_tag_iter(&self) -> Option<PairlistTagIter> {
-        match self.sexptype() {
-            LISTSXP | LANGSXP | DOTSXP => unsafe {
-                Some(PairlistTagIter {
-                    root_obj: self.into(),
-                    list_elem: self.get(),
-                })
-            },
-            _ => None,
-        }
-    }
-
-    /// Get an iterator over a list (VECSXP).
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let mut robj = list!(1, 2, 3);
-    ///     let objects : Vec<_> = robj.as_list_iter().unwrap().collect();
-    ///     assert_eq!(objects, vec![r!(1), r!(2), r!(3)]);
-    /// }
-    /// ```
-    pub fn as_list_iter(&self) -> Option<ListIter> {
-        match self.sexptype() {
-            VECSXP | EXPRSXP | WEAKREFSXP => Some(ListIter {
-                vector: self.into(),
-                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.
@@ -532,31 +284,4 @@ impl Robj {
             _ => None,
         }
     }
-
-    /// Iterate over an environment.
-    pub fn as_env_iter(&self) -> Option<EnvIter> {
-        if self.is_environment() {
-            unsafe {
-                let hashtab = new_owned(HASHTAB(self.get()));
-                let frame = new_owned(FRAME(self.get()));
-                if hashtab.is_null() && frame.is_pairlist() {
-                    Some(EnvIter {
-                        hash_table: ListIter::new(),
-                        pairlisttags: frame.as_pairlist_tag_iter().unwrap(),
-                        pairlist: frame.as_pairlist_iter().unwrap(),
-                    })
-                } else if hashtab.is_list() {
-                    Some(EnvIter {
-                        hash_table: hashtab.as_list_iter().unwrap(),
-                        pairlist: PairlistIter::new(),
-                        pairlisttags: PairlistTagIter::new(),
-                    })
-                } else {
-                    None
-                }
-            }
-        } else {
-            None
-        }
-    }
 }
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index a15981853..a9ec8bd6c 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -63,9 +63,9 @@
 //!     let list = list!(a = 1, b = 2);
 //!    
 //!     // An unnamed list (of R objects) using the List wrapper.
-//!     let list = r!(List(vec![1, 2, 3]));
-//!     let list = r!(List(vec!["a", "b", "c"]));
-//!     let list = r!(List(&[r!("a"), r!(1), r!(2.0)]));
+//!     let list = r!(List::from_values(vec![1, 2, 3]));
+//!     let list = r!(List::from_values(vec!["a", "b", "c"]));
+//!     let list = r!(List::from_values(&[r!("a"), r!(1), r!(2.0)]));
 //!
 //!     // A symbol
 //!     let sym = sym!(wombat);
@@ -206,7 +206,6 @@ pub mod functions;
 pub mod iter;
 pub mod lang_macros;
 pub mod logical;
-pub mod matrix;
 pub mod metadata;
 pub mod ownership;
 pub mod prelude;
@@ -219,6 +218,9 @@ pub mod wrapper;
 #[cfg(feature = "ndarray")]
 pub mod robj_ndarray;
 
+pub use std::convert::{TryFrom, TryInto};
+pub use std::ops::Deref;
+
 //////////////////////////////////////////////////
 // Note these pub use statements are deprectaed
 //
@@ -230,7 +232,6 @@ pub use error::*;
 pub use functions::*;
 pub use lang_macros::*;
 pub use logical::*;
-pub use matrix::*;
 pub use rmacros::*;
 pub use robj::*;
 pub use thread_safety::{
@@ -546,7 +547,7 @@ mod tests {
     }
 
     #[extendr]
-    pub fn matrix(x: RMatrix<&[f64]>) -> RMatrix<&[f64]> {
+    pub fn matrix(x: RMatrix<f64>) -> RMatrix<f64> {
         x
     }
 
@@ -709,13 +710,13 @@ mod tests {
                 // #[extendr]
                 // pub fn matrix(x: Matrix<&[f64]>) -> Matrix<&[f64]> { x }
 
-                let m = RMatrix::new([1., 2.], 1, 2);
+                let m = RMatrix::new_matrix(1, 2, |r, c| if r == c {1.0} else {0.});
                 let robj = r!(m);
                 assert_eq!(new_owned(wrap__matrix(robj.get())), robj);
 
                 // #[extendr]
                 // pub fn hash_map(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> { x }
-                let robj = r!(List(&[1, 2]));
+                let robj = r!(List::from_values(&[1, 2]));
                 robj.set_attrib(names_symbol(), r!(["a", "b"]))?;
                 let res = new_owned(wrap__hash_map(robj.get()));
                 assert_eq!(res.len(), 2);
diff --git a/extendr-api/src/matrix.rs b/extendr-api/src/matrix.rs
deleted file mode 100644
index e4e3e1708..000000000
--- a/extendr-api/src/matrix.rs
+++ /dev/null
@@ -1,316 +0,0 @@
-//! Wrappers for matrices with deferred arithmetic.
-
-use crate::*;
-use std::ops::{Index, IndexMut};
-
-/// Wrapper for creating and using matrices and arrays.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let matrix = RMatrix::new([
-///          1., 2., 3.,
-///          4., 5., 6.], 3, 2);
-///     let robj = r!(matrix);
-///     assert_eq!(robj.is_matrix(), true);
-///     assert_eq!(robj.nrows(), 3);
-///     assert_eq!(robj.ncols(), 2);
-///
-///     let matrix2 : RMatrix<&[f64]> = robj.as_matrix().ok_or("error")?;
-///     assert_eq!(matrix2.data().len(), 6);
-///     assert_eq!(matrix2.nrows(), 3);
-///     assert_eq!(matrix2.ncols(), 2);
-/// }
-/// ```
-#[derive(Debug, PartialEq)]
-pub struct RArray<T, D> {
-    data: T,
-    dim: D,
-}
-
-pub type RColumn<T> = RArray<T, [usize; 1]>;
-pub type RMatrix<T> = RArray<T, [usize; 2]>;
-pub type RMatrix3D<T> = RArray<T, [usize; 3]>;
-
-const BASE: usize = 0;
-
-trait Offset<D> {
-    /// Get the offset into the array for a given index.
-    fn offset(&self, idx: D) -> usize;
-}
-
-impl<T> Offset<[usize; 1]> for RArray<T, [usize; 1]> {
-    /// Get the offset into the array for a given index.
-    fn offset(&self, index: [usize; 1]) -> usize {
-        if index[0] - BASE > self.dim[0] {
-            panic!("array index: row overflow");
-        }
-        index[0] - BASE
-    }
-}
-
-impl<T> Offset<[usize; 2]> for RArray<T, [usize; 2]> {
-    /// Get the offset into the array for a given index.
-    fn offset(&self, index: [usize; 2]) -> usize {
-        if index[0] - BASE > self.dim[0] {
-            panic!("matrix index: row overflow");
-        }
-        if index[1] - BASE > self.dim[1] {
-            panic!("matrix index: column overflow");
-        }
-        (index[0] - BASE) + self.dim[0] * (index[1] - BASE)
-    }
-}
-
-impl<T> Offset<[usize; 3]> for RArray<T, [usize; 3]> {
-    /// Get the offset into the array for a given index.
-    fn offset(&self, index: [usize; 3]) -> usize {
-        if index[0] - BASE > self.dim[0] {
-            panic!("RMatrix3D index: row overflow");
-        }
-        if index[1] - BASE > self.dim[1] {
-            panic!("RMatrix3D index: column overflow");
-        }
-        if index[2] - BASE > self.dim[2] {
-            panic!("RMatrix3D index: submatrix overflow");
-        }
-        (index[0] - BASE) + self.dim[0] * (index[1] - BASE + self.dim[1] * (index[2] - BASE))
-    }
-}
-
-impl<T, D> RArray<T, D> {
-    /// Get the underlying data fro this array.
-    pub fn data(&self) -> &T {
-        &self.data
-    }
-
-    /// Get the dimensions for this array.
-    pub fn dim(&self) -> &D {
-        &self.dim
-    }
-}
-
-impl<T> RColumn<T> {
-    /// Make a new vector type.
-    pub fn new(data: T, nrows: usize) -> Self {
-        let dim = [nrows];
-        Self { data, dim }
-    }
-
-    /// Get the number of rows.
-    pub fn nrows(&self) -> usize {
-        self.dim[0]
-    }
-}
-
-impl<T> RMatrix<T> {
-    /// Create a new matrix wrapper.
-    pub fn new(data: T, nrows: usize, ncols: usize) -> Self {
-        let dim = [nrows, ncols];
-        Self { data, dim }
-    }
-
-    /// Get the number of rows.
-    pub fn nrows(&self) -> usize {
-        self.dim[0]
-    }
-
-    /// Get the number of columns.
-    pub fn ncols(&self) -> usize {
-        self.dim[1]
-    }
-}
-
-impl<T> RMatrix3D<T> {
-    /// Create a new matrix wrapper.
-    pub fn new(data: T, nrows: usize, ncols: usize, nsub: usize) -> Self {
-        let dim = [nrows, ncols, nsub];
-        Self { data, dim }
-    }
-
-    /// Get the number of rows.
-    pub fn nrows(&self) -> usize {
-        self.dim[0]
-    }
-
-    /// Get the number of columns.
-    pub fn ncols(&self) -> usize {
-        self.dim[1]
-    }
-
-    /// Get the number of submatrices.
-    pub fn nsub(&self) -> usize {
-        self.dim[2]
-    }
-}
-
-impl<T> From<RColumn<T>> for Robj
-where
-    T: Into<Robj>,
-{
-    fn from(array: RColumn<T>) -> Self {
-        let res = array.data.into();
-        res
-    }
-}
-
-impl<T> From<RArray<T, [usize; 2]>> for Robj
-where
-    T: Into<Robj>,
-{
-    fn from(array: RArray<T, [usize; 2]>) -> Self {
-        let res = array.data.into();
-        let dim = [array.dim[0] as i32, array.dim[1] as i32];
-        res.set_attrib(dim_symbol(), dim)
-            .expect("From<Matrix> failed")
-            .set_attrib(class_symbol(), "matrix")
-            .expect("From<Matrix> failed")
-    }
-}
-
-impl<T> From<RArray<T, [usize; 3]>> for Robj
-where
-    T: Into<Robj>,
-{
-    fn from(array: RArray<T, [usize; 3]>) -> Self {
-        let res = array.data.into();
-        let dim = [
-            array.dim[0] as i32,
-            array.dim[1] as i32,
-            array.dim[2] as i32,
-        ];
-        res.set_attrib(dim_symbol(), dim)
-            .expect("From<Matrix3D> failed")
-            .set_attrib(class_symbol(), "array")
-            .expect("From<Matrix3D> failed")
-    }
-}
-
-impl Robj {
-    pub fn as_vector<'a, E>(&self) -> Option<RColumn<&'a [E]>>
-    where
-        Self: AsTypedSlice<'a, E>,
-    {
-        if let Some(data) = self.as_typed_slice() {
-            let dim = [self.nrows() as usize];
-            return Some(RArray { data, dim });
-        }
-        None
-    }
-
-    pub fn as_matrix<'a, E>(&self) -> Option<RMatrix<&'a [E]>>
-    where
-        Self: AsTypedSlice<'a, E>,
-    {
-        if self.is_matrix() {
-            if let Some(data) = self.as_typed_slice() {
-                let dim = [self.nrows() as usize, self.ncols() as usize];
-                return Some(RArray { data, dim });
-            }
-        }
-        None
-    }
-
-    pub fn as_matrix3d<'a, E>(&self) -> Option<RMatrix3D<&'a [E]>>
-    where
-        Self: AsTypedSlice<'a, E>,
-    {
-        if self.is_array() {
-            if let Some(data) = self.as_typed_slice() {
-                if let Some(dim) = self.dim() {
-                    let dim: Vec<_> = dim.collect();
-                    let dim = [dim[0] as usize, dim[1] as usize, dim[2] as usize];
-                    return Some(RArray { data, dim });
-                }
-            }
-        }
-        None
-    }
-}
-
-impl<T> Index<[usize; 2]> for RArray<T, [usize; 2]>
-where
-    T: Index<usize>,
-{
-    type Output = <T as Index<usize>>::Output;
-
-    /// Zero-based indexing in row, column order.
-    ///
-    /// Panics if out of bounds.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let matrix = RMatrix::new(vec![
-    ///          1., 2., 3.,
-    ///          4., 5., 6.], 3, 2);
-    ///     assert_eq!(matrix[[0, 0]], 1.);
-    ///     assert_eq!(matrix[[1, 0]], 2.);
-    ///     assert_eq!(matrix[[2, 1]], 6.);
-    /// }
-    /// ```
-    fn index(&self, index: [usize; 2]) -> &Self::Output {
-        &self.data[self.offset(index)]
-    }
-}
-
-impl<T> IndexMut<[usize; 2]> for RArray<T, [usize; 2]>
-where
-    T: IndexMut<usize>,
-{
-    /// Zero-based mutable indexing in row, column order.
-    ///
-    /// Panics if out of bounds.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let mut matrix = RMatrix::new(vec![0.; 6], 3, 2);
-    ///     matrix[[0, 0]] = 1.;
-    ///     matrix[[1, 0]] = 2.;
-    ///     matrix[[2, 0]] = 3.;
-    ///     assert_eq!(matrix, RMatrix::new(vec![1., 2., 3., 0., 0., 0.], 3, 2));
-    /// }
-    /// ```
-    fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output {
-        let offset = self.offset(index);
-        &mut self.data[offset]
-    }
-}
-
-#[test]
-fn matrix_ops() {
-    test! {
-        let vector = RColumn::new([1., 2., 3.], 3);
-        let robj = r!(vector);
-        assert_eq!(robj.is_vector(), true);
-        assert_eq!(robj.nrows(), 3);
-
-        let vector2 : RColumn<&[f64]> = robj.as_vector().ok_or("expected array")?;
-        assert_eq!(vector2.data().len(), 3);
-        assert_eq!(vector2.nrows(), 3);
-
-        let matrix = RMatrix::new([
-            1., 2., 3.,
-            4., 5., 6.], 3, 2);
-        let robj = r!(matrix);
-        assert_eq!(robj.is_matrix(), true);
-        assert_eq!(robj.nrows(), 3);
-        assert_eq!(robj.ncols(), 2);
-        let matrix2 : RMatrix<&[f64]> = robj.as_matrix().ok_or("expected matrix")?;
-        assert_eq!(matrix2.data().len(), 6);
-        assert_eq!(matrix2.nrows(), 3);
-        assert_eq!(matrix2.ncols(), 2);
-
-        let array = RMatrix3D::new([
-            1., 2.,  3., 4.,
-            5.,  6., 7., 8.], 2, 2, 2);
-        let robj = r!(array);
-        assert_eq!(robj.is_array(), true);
-        assert_eq!(robj.nrows(), 2);
-        assert_eq!(robj.ncols(), 2);
-        let array2 : RMatrix3D<&[f64]> = robj.as_matrix3d().ok_or("expected matrix3d")?;
-        assert_eq!(array2.data().len(), 8);
-        assert_eq!(array2.nrows(), 2);
-        assert_eq!(array2.ncols(), 2);
-        assert_eq!(array2.nsub(), 2);
-    }
-}
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index 9e920a1b2..c181100fa 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -42,7 +42,7 @@ pub struct Metadata {
 
 impl From<Arg> for Robj {
     fn from(val: Arg) -> Self {
-        List(&[r!(val.name), r!(val.arg_type)])
+        List::from_values(&[r!(val.name), r!(val.arg_type)])
             .into_robj()
             .set_names(&["name", "arg_type"])
             .expect("From<Arg> failed")
@@ -51,10 +51,10 @@ impl From<Arg> for Robj {
 
 impl From<Func> for Robj {
     fn from(val: Func) -> Self {
-        List(&[
+        List::from_values(&[
             r!(val.doc),
             r!(val.name),
-            r!(List(val.args)),
+            r!(List::from_values(val.args)),
             r!(val.return_type),
             r!(val.hidden),
         ])
@@ -66,19 +66,27 @@ impl From<Func> for Robj {
 
 impl From<Impl> for Robj {
     fn from(val: Impl) -> Self {
-        List(&[r!(val.doc), r!(val.name), r!(List(val.methods))])
-            .into_robj()
-            .set_names(&["doc", "name", "methods"])
-            .expect("From<Impl> failed")
+        List::from_values(&[
+            r!(val.doc),
+            r!(val.name),
+            r!(List::from_values(val.methods)),
+        ])
+        .into_robj()
+        .set_names(&["doc", "name", "methods"])
+        .expect("From<Impl> failed")
     }
 }
 
 impl From<Metadata> for Robj {
     fn from(val: Metadata) -> Self {
-        List(&[r!(val.name), r!(List(val.functions)), r!(List(val.impls))])
-            .into_robj()
-            .set_names(&["name", "functions", "impls"])
-            .expect("From<Metadata> failed")
+        List::from_values(&[
+            r!(val.name),
+            r!(List::from_values(val.functions)),
+            r!(List::from_values(val.impls)),
+        ])
+        .into_robj()
+        .set_names(&["name", "functions", "impls"])
+        .expect("From<Metadata> failed")
     }
 }
 
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 33d3e184d..b7e8f27ff 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -11,15 +11,18 @@ pub use super::{
 pub use super::error::{Error, Result};
 
 pub use super::functions::{
-    base_env, base_namespace, base_symbol, blank_scalar_string, blank_string, brace_symbol,
-    bracket_2_symbol, bracket_symbol, class_symbol, current_env, device_symbol, dim_symbol,
-    dimnames_symbol, dollar_symbol, dot_defined, dot_method, dot_package_name, dot_target,
-    dots_symbol, double_colon_symbol, empty_env, eval_string, find_namespace, global_env,
-    global_function, global_var, lastvalue_symbol, levels_symbol, local_var, missing_arg,
-    mode_symbol, na_rm_symbol, na_str, na_string, name_symbol, names_symbol, namespace_env_symbol,
-    namespace_registry, new_env, new_env_with_capacity, nil_value, package_symbol, parse,
+    base_env, base_namespace, blank_scalar_string, blank_string, current_env, empty_env,
+    eval_string, find_namespace, find_namespaced_function, global_env, global_function, global_var,
+    local_var, na_str, na_string, namespace_registry, nil_value, parse, srcref,
+};
+
+pub use super::wrapper::symbol::{
+    base_symbol, brace_symbol, bracket_2_symbol, bracket_symbol, class_symbol, device_symbol,
+    dim_symbol, dimnames_symbol, dollar_symbol, dot_defined, dot_method, dot_package_name,
+    dot_target, dots_symbol, double_colon_symbol, lastvalue_symbol, levels_symbol, missing_arg,
+    mode_symbol, na_rm_symbol, name_symbol, names_symbol, namespace_env_symbol, package_symbol,
     previous_symbol, quote_symbol, row_names_symbol, seeds_symbol, sort_list_symbol, source_symbol,
-    spec_symbol, srcref, triple_colon_symbol, tsp_symbol, unbound_value,
+    spec_symbol, triple_colon_symbol, tsp_symbol, unbound_value,
 };
 
 pub use crate::{append, append_lang, append_with_name, args, call, lang, make_lang};
@@ -30,7 +33,7 @@ pub use crate::{
 
 pub use super::logical::Bool;
 
-pub use super::matrix::{RArray, RColumn, RMatrix, RMatrix3D};
+pub use super::wrapper::{RArray, RColumn, RMatrix, RMatrix3D};
 
 pub use super::robj::{IntoRobj, Robj, RobjItertools};
 
@@ -39,7 +42,8 @@ pub use super::thread_safety::{
 };
 
 pub use super::wrapper::{
-    Character, Env, Expr, Function, Lang, List, Nullable, Pairlist, Primitive, Promise, Raw, Symbol,
+    Character, EnvIter, Environment, Expression, FromList, Function, Language, List, ListIter,
+    Nullable, Pairlist, Primitive, Promise, Raw, Symbol,
 };
 
 #[cfg(feature = "ndarray")]
@@ -50,6 +54,8 @@ pub use ndarray::*;
 
 pub use extendr_macros::{extendr, extendr_module};
 
-pub use super::iter::{
-    EnvIter, Int, ListIter, Logical, PairlistIter, PairlistTagIter, Real, StrIter,
-};
+pub use super::iter::{Int, Logical, Real, StrIter};
+
+pub use std::convert::{TryFrom, TryInto};
+
+pub use std::ops::Index;
diff --git a/extendr-api/src/rmacros.rs b/extendr-api/src/rmacros.rs
index ed2e078bd..266942126 100644
--- a/extendr-api/src/rmacros.rs
+++ b/extendr-api/src/rmacros.rs
@@ -100,13 +100,13 @@ macro_rules! global {
 /// test! {
 ///
 /// let wombat = sym!(wombat);
-/// assert_eq!(wombat, r!(Symbol("wombat")));
+/// assert_eq!(wombat, r!(Symbol::from_str("wombat")));
 /// }
 /// ```
 #[macro_export]
 macro_rules! sym {
     ($($tokens: tt)*) => {
-        Robj::from(Symbol(stringify!($($tokens)*)))
+        Robj::from(Symbol::from_str(stringify!($($tokens)*)))
     };
 }
 
@@ -116,8 +116,8 @@ macro_rules! sym {
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-/// let mylist = list!(x=1, y=2);
-/// assert_eq!(mylist, r!(List(&[r!(1), r!(2)])));
+///     let mylist = list!(x=1, y=2);
+///     assert_eq!(mylist, r!(List::from_pairs(vec![("x", r!(1)), ("y", r!(2))])));
 /// }
 /// ```
 ///
@@ -138,8 +138,8 @@ macro_rules! list {
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-/// let mylist = pairlist!(x=1, y=2);
-/// ///assert_eq!(mylist, r!(Pairlist(&[r!(1), r!(2)])));
+///     let mylist = pairlist!(x=1, y=2);
+///     assert_eq!(mylist, r!(Pairlist::from_pairs(vec![("x", r!(1)), ("y", r!(2))])));
 /// }
 /// ```
 ///
@@ -160,8 +160,9 @@ macro_rules! pairlist {
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-/// let mydata = data_frame!(x=1, y=2);
-/// assert_eq!(mydata, r!(List(&[r!(1), r!(2)])));
+///     let mydata = data_frame!(x=1, y=2);
+///     assert_eq!(mydata.inherits("data.frame"), true);
+///     //assert_eq!(mydata, r!(List::from_pairs(vec![("x", r!(1)), ("y", r!(2))])).set_class(&["data.frame"])?);
 /// }
 /// ```
 ///
diff --git a/extendr-api/src/robj/from_robj.rs b/extendr-api/src/robj/from_robj.rs
index 74c777b97..737ecb912 100644
--- a/extendr-api/src/robj/from_robj.rs
+++ b/extendr-api/src/robj/from_robj.rs
@@ -152,7 +152,6 @@ macro_rules! impl_iter_from_robj {
 }
 
 impl_iter_from_robj!(StrIter, as_str_iter, "Not a character vector.");
-impl_iter_from_robj!(ListIter, as_list_iter, "Not a list.");
 impl_iter_from_robj!(Int, as_integer_iter, "Not an integer vector.");
 impl_iter_from_robj!(Real, as_real_iter, "Not a real vector.");
 impl_iter_from_robj!(Logical, as_logical_iter, "Not a logical vector.");
@@ -166,7 +165,7 @@ impl<'a> FromRobj<'a> for Robj {
 
 impl<'a> FromRobj<'a> for HashMap<String, Robj> {
     fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(iter) = robj.as_named_list_iter() {
+        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
             Ok(iter
                 .map(|(k, v)| (k.to_string(), v.to_owned()))
                 .collect::<HashMap<String, Robj>>())
@@ -178,7 +177,7 @@ impl<'a> FromRobj<'a> for HashMap<String, Robj> {
 
 impl<'a> FromRobj<'a> for HashMap<&str, Robj> {
     fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(iter) = robj.as_named_list_iter() {
+        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
             Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
         } else {
             Err("expected a list")
@@ -266,43 +265,28 @@ where
     }
 }
 
-// Symbol input parameters.
-impl<'a> FromRobj<'a> for Symbol<'a> {
-    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(x) = robj.as_symbol() {
-            Ok(x)
-        } else {
-            Err("Expected a symbol.")
-        }
-    }
-}
-
 // Matrix input parameters.
-impl<'a, T> FromRobj<'a> for RArray<&'a [T], [usize; 2]>
+impl<'a, T: 'a> FromRobj<'a> for RArray<T, [usize; 2]>
 where
     Robj: AsTypedSlice<'a, T>,
 {
     fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(x) = robj.as_matrix() {
-            Ok(x)
-        } else if !robj.as_typed_slice().is_some() {
-            Err("Matrix type mismatch.")
-        } else {
-            Err("Expected a matrix.")
+        match robj.as_matrix() {
+            Some(x) => Ok(x),
+            _ => Err("Expected a matrix."),
         }
     }
 }
 
 // Matrix input parameters.
-impl<'a, T> FromRobj<'a> for RMatrix3D<&'a [T]>
+impl<'a, T: 'a> FromRobj<'a> for RMatrix3D<T>
 where
     Robj: AsTypedSlice<'a, T>,
 {
     fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(x) = robj.as_matrix3d() {
-            Ok(x)
-        } else {
-            Err("Expected a 3d matrix.")
+        match robj.as_matrix3d() {
+            Some(x) => Ok(x),
+            _ => Err("Expected a matrix."),
         }
     }
 }
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index f7733cf8b..2d375c7ff 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -2,7 +2,7 @@ use super::*;
 use crate::single_threaded;
 use std::collections::HashMap;
 
-fn str_to_character(s: &str) -> SEXP {
+pub(crate) fn str_to_character(s: &str) -> SEXP {
     unsafe {
         if s.is_na() {
             R_NaString
@@ -44,13 +44,6 @@ where
     }
 }
 
-/// 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
@@ -534,7 +527,7 @@ impl From<Logical> for Robj {
 impl<'a> From<HashMap<&'a str, Robj>> for Robj {
     /// Convert a hashmap into a list.
     fn from(val: HashMap<&'a str, Robj>) -> Self {
-        let res: Robj = List(val.iter().map(|(_, v)| v)).into();
+        let res: Robj = List::from_values(val.iter().map(|(_, v)| v)).into();
         res.set_names(val.into_iter().map(|(k, _)| k)).unwrap()
     }
 }
@@ -542,6 +535,6 @@ impl<'a> From<HashMap<&'a str, Robj>> for Robj {
 impl<'a> From<Vec<Robj>> for Robj {
     /// Convert a vector of Robj into a list.
     fn from(val: Vec<Robj>) -> Self {
-        List(val.iter()).into()
+        List::from_values(val.iter()).into()
     }
 }
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index fbe4612e0..559c69810 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -18,10 +18,13 @@ use std::collections::HashMap;
 use std::iter::IntoIterator;
 use std::ops::{Range, RangeInclusive};
 
+// deprecated
 mod from_robj;
+
 mod into_robj;
 mod operators;
 mod rinternals;
+mod try_from_robj;
 
 #[cfg(test)]
 mod tests;
@@ -101,7 +104,7 @@ pub use rinternals::*;
 /// test! {
 ///     let abc = list!(a = 1, b = "x", c = vec![1, 2]);
 ///     let names : Vec<_> = abc.names().unwrap().collect();
-///     let names_and_values : Vec<_> = abc.as_named_list_iter().unwrap().collect();
+///     let names_and_values : Vec<_> = abc.as_list().unwrap().iter().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]))]);
 /// }
@@ -174,20 +177,20 @@ impl Robj {
     /// test! {
     ///     assert_eq!(r!(NULL).rtype(), RType::Null);
     ///     assert_eq!(sym!(xyz).rtype(), RType::Symbol);
-    ///     assert_eq!(r!(Pairlist{names_and_values: vec![("a", r!(1))]}).rtype(), RType::Pairlist);
+    ///     assert_eq!(r!(Pairlist::from_pairs(vec![("a", r!(1))])).rtype(), RType::Pairlist);
     ///     assert_eq!(R!(function() {})?.rtype(), RType::Function);
-    ///     assert_eq!(new_env().rtype(), RType::Enviroment);
+    ///     assert_eq!(Environment::new(global_env()).rtype(), RType::Enviroment);
     ///     assert_eq!(lang!("+", 1, 2).rtype(), RType::Language);
-    ///     assert_eq!(r!(Primitive("if")).rtype(), RType::Special);
-    ///     assert_eq!(r!(Primitive("+")).rtype(), RType::Builtin);
-    ///     assert_eq!(r!(Character("hello")).rtype(), RType::Character);
+    ///     assert_eq!(r!(Primitive::from_str("if")).rtype(), RType::Special);
+    ///     assert_eq!(r!(Primitive::from_str("+")).rtype(), RType::Builtin);
+    ///     assert_eq!(r!(Character::from_str("hello")).rtype(), RType::Character);
     ///     assert_eq!(r!(TRUE).rtype(), RType::Logical);
     ///     assert_eq!(r!(1).rtype(), RType::Integer);
     ///     assert_eq!(r!(1.0).rtype(), RType::Real);
     ///     assert_eq!(r!("1").rtype(), RType::String);
-    ///     assert_eq!(r!(List(&[1, 2])).rtype(), RType::List);
+    ///     assert_eq!(r!(List::from_values(&[1, 2])).rtype(), RType::List);
     ///     assert_eq!(parse("x + y")?.rtype(), RType::Expression);
-    ///     assert_eq!(r!(Raw(&[1_u8, 2, 3])).rtype(), RType::Raw);
+    ///     assert_eq!(r!(Raw::from_bytes(&[1_u8, 2, 3])).rtype(), RType::Raw);
     /// }
     /// ```
     pub fn rtype(&self) -> RType {
@@ -233,69 +236,6 @@ impl Robj {
         unsafe { Rf_xlength(self.get()) as usize }
     }
 
-    /// Get a variable from an enviroment, but not its ancestors.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///
-    /// let env = new_env();
-    /// env.set_local(sym!(x), "fred");
-    /// assert_eq!(env.local(sym!(x)), Some(r!("fred")));
-    /// }
-    /// ```
-    pub fn local<K: Into<Robj>>(&self, key: K) -> Option<Robj> {
-        let key = key.into();
-        if self.is_environment() && key.is_symbol() {
-            unsafe { Some(new_owned(Rf_findVarInFrame3(self.get(), key.get(), 1))) }
-        } else {
-            None
-        }
-    }
-
-    /// Set or define a variable in an enviroment.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///
-    /// let env = new_env();
-    /// env.set_local(sym!(x), "harry");
-    /// env.set_local(sym!(x), "fred");
-    /// assert_eq!(env.local(sym!(x)), Some(r!("fred")));
-    /// }
-    /// ```
-    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
-        let key = key.into();
-        let value = value.into();
-        if self.is_environment() && key.is_symbol() {
-            single_threaded(|| unsafe {
-                Rf_defineVar(key.get(), value.get(), self.get());
-            })
-        }
-    }
-
-    /// Get the parent of an environment.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///
-    /// let global_parent = global_env().parent().unwrap();
-    /// assert_eq!(global_parent.is_environment(), true);
-    /// assert_eq!(base_env().parent(), None);
-    /// assert_eq!(r!(1).parent(), None);
-    /// }
-    /// ```
-    pub fn parent(&self) -> Option<Robj> {
-        unsafe {
-            if self.is_environment() {
-                let parent = ENCLOS(self.get());
-                if Rf_isEnvironment(parent) != 0 && parent != R_EmptyEnv {
-                    return Some(new_owned(parent));
-                }
-            }
-            None
-        }
-    }
-
     /// Is this object is an NA scalar?
     /// Works for character, integer and numeric types.
     /// ```
@@ -493,7 +433,7 @@ impl Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let robj = r!(Raw(&[1, 2, 3]));
+    ///     let robj = r!(Raw::from_bytes(&[1, 2, 3]));
     ///     assert_eq!(robj.as_raw_slice().unwrap(), &[1, 2, 3]);
     /// }
     /// ```
@@ -535,10 +475,10 @@ impl Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let mut robj = r!(Raw(&[1, 2, 3]));
+    ///     let mut robj = r!(Raw::from_bytes(&[1, 2, 3]));
     ///     let slice = robj.as_raw_slice_mut().unwrap();
     ///     slice[1] = 100;
-    ///     assert_eq!(robj, r!(Raw(&[1, 100, 3])));
+    ///     assert_eq!(robj, r!(Raw::from_bytes(&[1, 100, 3])));
     /// }
     /// ```
     pub fn as_raw_slice_mut(&mut self) -> Option<&mut [u8]> {
@@ -700,10 +640,7 @@ impl Robj {
             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(Error::EvalError {
-                    code: r!(self),
-                    error,
-                })
+                Err(Error::EvalError(self.clone()))
             } else {
                 Ok(new_owned(res))
             }
@@ -822,8 +759,8 @@ impl Robj {
     /// test! {
     ///
     ///    let mut robj = r!("hello");
-    ///    robj.set_attrib(Symbol("xyz"), 1);
-    ///    assert_eq!(robj.get_attrib(Symbol("xyz")), Some(r!(1)));
+    ///    robj.set_attrib(sym!(xyz), 1);
+    ///    assert_eq!(robj.get_attrib(sym!(xyz)), Some(r!(1)));
     /// }
     /// ```
     pub fn get_attrib<'a, N>(&self, name: N) -> Option<Robj>
@@ -852,8 +789,8 @@ impl Robj {
     /// use extendr_api::prelude::*;
     /// test! {
     ///
-    ///    let mut robj = r!("hello").set_attrib(Symbol("xyz"), 1)?;
-    ///    assert_eq!(robj.get_attrib(Symbol("xyz")), Some(r!(1)));
+    ///    let mut robj = r!("hello").set_attrib(sym!(xyz), 1)?;
+    ///    assert_eq!(robj.get_attrib(sym!(xyz)), Some(r!(1)));
     /// }
     /// ```
     pub fn set_attrib<N, V>(&self, name: N, value: V) -> Result<Robj>
@@ -881,7 +818,7 @@ impl Robj {
     /// }
     /// ```
     pub fn names(&self) -> Option<StrIter> {
-        if let Some(names) = self.get_attrib(names_symbol()) {
+        if let Some(names) = self.get_attrib(wrapper::symbol::names_symbol()) {
             names.as_str_iter()
         } else {
             None
@@ -897,21 +834,21 @@ impl Robj {
     /// test! {
     ///     let mut obj = r!([1, 2, 3]).set_names(&["a", "b", "c"]).unwrap();
     ///     assert_eq!(obj.names().unwrap().collect::<Vec<_>>(), vec!["a", "b", "c"]);
-    ///     assert_eq!(r!([1, 2, 3]).set_names(&["a", "b"]), Err(Error::NamesLengthMismatch));
+    ///     assert_eq!(r!([1, 2, 3]).set_names(&["a", "b"]), Err(Error::NamesLengthMismatch(r!(["a", "b"]))));
     /// }
     /// ```
     pub fn set_names<T>(&self, names: T) -> Result<Robj>
     where
         T: IntoIterator,
-        T::IntoIter: Iterator,
+        T::IntoIter: ExactSizeIterator,
         T::Item: ToVectorValue + AsRef<str>,
     {
         let iter = names.into_iter();
         let robj = iter.collect_robj();
         if robj.len() == self.len() {
-            self.set_attrib(names_symbol(), robj)
+            self.set_attrib(wrapper::symbol::names_symbol(), robj)
         } else {
-            Err(Error::NamesLengthMismatch)
+            Err(Error::NamesLengthMismatch(robj))
         }
     }
 
@@ -926,7 +863,7 @@ impl Robj {
     /// }
     /// ```
     pub fn dim(&self) -> Option<Int> {
-        if let Some(dim) = self.get_attrib(dim_symbol()) {
+        if let Some(dim) = self.get_attrib(wrapper::symbol::dim_symbol()) {
             dim.as_integer_iter()
         } else {
             None
@@ -943,31 +880,13 @@ impl Robj {
     /// }
     /// ```
     pub fn dimnames(&self) -> Option<ListIter> {
-        if let Some(names) = self.get_attrib(dimnames_symbol()) {
-            names.as_list_iter()
+        if let Some(names) = self.get_attrib(wrapper::symbol::dimnames_symbol()) {
+            names.as_list().map(|v| v.values())
         } else {
             None
         }
     }
 
-    /// Return an iterator over names and values of a list if they exist.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///    let list = list!(a = 1, b = 2, c = 3);
-    ///    let names_and_values : Vec<_> = list.as_named_list_iter().unwrap().collect();
-    ///    assert_eq!(names_and_values, vec![("a", r!(1)), ("b", r!(2)), ("c", r!(3))]);
-    /// }
-    /// ```
-    pub fn as_named_list_iter(&self) -> Option<NamedListIter> {
-        if let Some(names) = self.names() {
-            if let Some(values) = self.as_list_iter() {
-                return Some(names.zip(values));
-            }
-        }
-        None
-    }
-
     /// Get the class attribute as a string iterator if one exists.
     /// ```
     /// use extendr_api::prelude::*;
@@ -978,14 +897,15 @@ impl Robj {
     /// }
     /// ```
     pub fn class(&self) -> Option<StrIter> {
-        if let Some(class) = self.get_attrib(class_symbol()) {
+        if let Some(class) = self.get_attrib(wrapper::symbol::class_symbol()) {
             class.as_str_iter()
         } else {
             None
         }
     }
 
-    /// Set the class attribute from a string iterator.
+    /// Set the class attribute from a string iterator, returning
+    /// a new object.
     ///
     /// May return an error for some class names.
     /// ```
@@ -999,11 +919,11 @@ impl Robj {
     pub fn set_class<T>(&self, class: T) -> Result<Robj>
     where
         T: IntoIterator,
-        T::IntoIter: Iterator,
+        T::IntoIter: ExactSizeIterator,
         T::Item: ToVectorValue + AsRef<str>,
     {
         let iter = class.into_iter();
-        self.set_attrib(class_symbol(), iter.collect_robj())
+        self.set_attrib(wrapper::symbol::class_symbol(), iter.collect_robj())
     }
 
     /// Return true if this class inherits this class.
@@ -1032,26 +952,12 @@ impl Robj {
     /// }
     /// ```
     pub fn levels(&self) -> Option<StrIter> {
-        if let Some(levels) = self.get_attrib(levels_symbol()) {
+        if let Some(levels) = self.get_attrib(wrapper::symbol::levels_symbol()) {
             levels.as_str_iter()
         } else {
             None
         }
     }
-
-    /// Get the names in an environment.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///    let names_and_values : std::collections::HashMap<_, _> = (0..4).map(|i| (format!("n{}", i), r!(i))).collect();
-    ///    let env = r!(Env{parent: global_env(), names_and_values});
-    ///    assert_eq!(env.ls().unwrap(), vec!["n0", "n1", "n2", "n3"]);
-    /// }
-    /// ```
-    pub fn ls(&self) -> Option<Vec<&str>> {
-        self.as_env_iter()
-            .map(|iter| iter.map(|(k, _)| k).collect::<Vec<_>>())
-    }
 }
 
 #[doc(hidden)]
@@ -1093,42 +999,9 @@ impl PartialEq<Robj> for Robj {
             if self.get() == rhs.get() {
                 return true;
             }
-            if self.sexptype() == rhs.sexptype() && self.len() == rhs.len() {
-                let lsexp = self.get();
-                let rsexp = rhs.get();
-                match self.sexptype() {
-                    NILSXP => true,
-                    SYMSXP => PRINTNAME(lsexp) == PRINTNAME(rsexp),
-                    LISTSXP | LANGSXP | DOTSXP => self
-                        .as_pairlist_iter()
-                        .unwrap()
-                        .eq(rhs.as_pairlist_iter().unwrap()),
-                    CLOSXP => false,
-                    ENVSXP => false, // objects must match.
-                    PROMSXP => false,
-                    SPECIALSXP => false,
-                    BUILTINSXP => false,
-                    CHARSXP => self.as_character() == rhs.as_character(),
-                    LGLSXP => self.as_logical_slice() == rhs.as_logical_slice(),
-                    INTSXP => self.as_integer_slice() == rhs.as_integer_slice(),
-                    REALSXP => self.as_real_slice() == rhs.as_real_slice(),
-                    CPLXSXP => false,
-                    ANYSXP => false,
-                    VECSXP | EXPRSXP | WEAKREFSXP => {
-                        self.as_list_iter().unwrap().eq(rhs.as_list_iter().unwrap())
-                    }
-                    STRSXP => self.as_str_iter().unwrap().eq(rhs.as_str_iter().unwrap()),
-                    BCODESXP => false,
-                    EXTPTRSXP => false,
-                    RAWSXP => self.as_raw_slice() == rhs.as_raw_slice(),
-                    S4SXP => false,
-                    NEWSXP => false,
-                    FREESXP => false,
-                    _ => false,
-                }
-            } else {
-                false
-            }
+
+            // see https://github.com/hadley/r-internals/blob/master/misc.md
+            R_compute_identical(self.get(), rhs.get(), 16) != 0
         }
     }
 }
@@ -1144,11 +1017,24 @@ impl std::fmt::Debug for Robj {
                 } else if self.is_unbound_value() {
                     write!(f, "unbound_value()")
                 } else {
-                    write!(f, "sym!({})", self.as_symbol().unwrap().0)
+                    write!(f, "sym!({})", self.as_symbol().unwrap().as_str())
                 }
             }
-            LISTSXP => write!(f, "r!({:?})", self.as_pairlist().unwrap()),
-            CLOSXP => write!(f, "r!(Function())"),
+            LISTSXP => {
+                let pairlist = self.as_pairlist().unwrap().iter();
+                write!(f, "r!({:?})", pairlist)
+            }
+            CLOSXP => {
+                let func = self.as_function().unwrap();
+                let formals = func.formals();
+                let body = func.body();
+                let enviroment = func.environment();
+                write!(
+                    f,
+                    "r!(Function::from_parts({:?}, {:?}, {:?}))",
+                    formals, body, enviroment
+                )
+            }
             ENVSXP => unsafe {
                 let sexp = self.get();
                 if sexp == R_GlobalEnv {
@@ -1158,26 +1044,33 @@ impl std::fmt::Debug for Robj {
                 } else if sexp == R_EmptyEnv {
                     write!(f, "empty_env()")
                 } else {
-                    write!(f, "r!({:?})", self.as_environment().unwrap())
+                    write!(f, "r!(Environment::from_pairs(...))")
                 }
             },
-            PROMSXP => write!(f, "r!(Promise())"),
-            LANGSXP => write!(f, "r!({:?})", self.as_lang().unwrap()),
+            PROMSXP => {
+                let p = self.as_promise().unwrap();
+                write!(
+                    f,
+                    "r!(Promise::from_parts({:?}, {:?}))",
+                    p.code(),
+                    p.environment()
+                )
+            }
+            LANGSXP => write!(
+                f,
+                "r!(Language::from_values({:?}))",
+                self.as_language().unwrap().values().collect::<Vec<_>>()
+            ),
             SPECIALSXP => write!(f, "r!(Special())"),
             BUILTINSXP => write!(f, "r!(Builtin())"),
-            CHARSXP => write!(f, "r!({:?})", self.as_character().unwrap()),
+            CHARSXP => {
+                let c = Character::try_from(self.clone()).unwrap();
+                write!(f, "r!(Character::from_str({:?}))", c.as_str())
+            }
             LGLSXP => {
                 let slice = self.as_logical_slice().unwrap();
                 if slice.len() == 1 {
-                    write!(
-                        f,
-                        "{}",
-                        if slice[0].0 == 0 {
-                            "r!(FALSE)"
-                        } else {
-                            "r!(TRUE)"
-                        }
-                    )
+                    write!(f, "r!({:?})", slice[0])
                 } else {
                     write!(f, "r!({:?})", slice)
                 }
@@ -1198,13 +1091,20 @@ impl std::fmt::Debug for Robj {
                     write!(f, "r!({:?})", slice)
                 }
             }
-            VECSXP => write!(f, "r!({:?})", self.as_list().unwrap()),
-            EXPRSXP => write!(f, "r!({:?})", self.as_expr().unwrap()),
-            WEAKREFSXP => write!(
+            VECSXP => {
+                let list = self.as_list().unwrap();
+                if self.names().is_some() {
+                    write!(f, "r!(List::from_pairs({:?}))", list.iter())
+                } else {
+                    write!(f, "r!(List::from_values({:?}))", list.values())
+                }
+            }
+            EXPRSXP => write!(
                 f,
-                "r!(Weakref({:?}))",
-                self.as_list_iter().unwrap().collect::<Vec<_>>()
+                "r!(Expression::from_values({:?}))",
+                self.as_expression().unwrap().values()
             ),
+            WEAKREFSXP => write!(f, "r!(Weakref())"),
             // CPLXSXP => false,
             STRSXP => {
                 write!(f, "r!([")?;
@@ -1224,13 +1124,21 @@ impl std::fmt::Debug for Robj {
             BCODESXP => write!(f, "r!(Bcode())"),
             EXTPTRSXP => write!(f, "r!(Extptr())"),
             RAWSXP => {
-                write!(f, "r!({:?})", self.as_raw().unwrap())
+                write!(
+                    f,
+                    "r!(Raw::from_bytes({:?}))",
+                    self.as_raw().unwrap().as_slice()
+                )
             }
             S4SXP => write!(f, "r!(S4())"),
             NEWSXP => write!(f, "r!(New())"),
             FREESXP => write!(f, "r!(Free())"),
             _ => write!(f, "??"),
+        }?;
+        if let Some(c) = self.class() {
+            write!(f, ".set_class({:?}", c)?;
         }
+        Ok(())
     }
 }
 
diff --git a/extendr-api/src/robj/operators.rs b/extendr-api/src/robj/operators.rs
index 54e8fd208..480d9e80b 100644
--- a/extendr-api/src/robj/operators.rs
+++ b/extendr-api/src/robj/operators.rs
@@ -9,18 +9,17 @@ impl Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    /// let env = r!(Env{
-    ///    parent: global_env(),
-    ///    names_and_values: vec![("a".to_string(), r!(1)), ("b".to_string(), r!(2))]});
+    /// let env = Environment::from_pairs(global_env(),
+    ///    vec![("a".to_string(), r!(1)), ("b".to_string(), r!(2))]);
     /// assert_eq!(env.dollar("a").unwrap(), r!(1));
     /// assert_eq!(env.dollar("b").unwrap(), r!(2));
     /// }
     /// ```
     pub fn dollar<'a, T>(&self, symbol: T) -> Result<Robj>
     where
-        Symbol<'a>: From<T>,
+        T: AsRef<str>,
     {
-        let symbol: Symbol = Symbol::from(symbol);
+        let symbol: Symbol = Symbol::from_str(symbol.as_ref());
         call!("$", self, symbol)
     }
 
@@ -60,11 +59,10 @@ impl Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    /// let x = r!(Symbol("x"));
-    /// let y = r!(Symbol("y"));
-    /// let tilde = x.tilde(y).unwrap();
-    /// assert_eq!(tilde, r!(Lang(&[r!(Symbol("~")), r!(Symbol("x")), r!(Symbol("y"))])));
-    /// assert_eq!(tilde.inherits("formula"), true);
+    ///     let x = r!(Symbol::from_str("x"));
+    ///     let y = r!(Symbol::from_str("y"));
+    ///     let tilde = x.tilde(y).unwrap();
+    ///     assert_eq!(tilde.inherits("formula"), true);
     /// }
     /// ```
     pub fn tilde<T>(&self, rhs: T) -> Result<Robj>
@@ -78,8 +76,8 @@ impl Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    /// let base = r!(Symbol("base"));
-    /// let env = r!(Symbol(".getNamespace"));
+    /// let base = r!(Symbol::from_str("base"));
+    /// let env = r!(Symbol::from_str(".getNamespace"));
     /// let base_env = base.double_colon(env).unwrap();
     /// assert_eq!(base_env.is_function(), true);
     /// }
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 44f3308b9..73a79be2f 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -31,7 +31,7 @@ impl Robj {
     }
 
     /// Return true if this is an expression.
-    pub fn is_expr(&self) -> bool {
+    pub fn is_expression(&self) -> bool {
         unsafe { Rf_isExpression(self.get()) != 0 }
     }
 
@@ -95,8 +95,8 @@ impl Robj {
         single_threaded(|| unsafe { new_owned(Rf_allocMatrix(sexptype, rows, cols)) })
     }
 
-    /// Compatible way to duplicate an object. Use obj.clone() instead
-    /// for Rust compatibility.
+    /// Do a deep copy of this object.
+    /// Note that clone() only adds a reference.
     pub fn duplicate(&self) -> Self {
         single_threaded(|| unsafe { new_owned(Rf_duplicate(self.get())) })
     }
@@ -116,10 +116,10 @@ impl Robj {
     ///    // assert!(base_env().find_function(sym!(qwertyuiop)).is_none());
     /// }
     /// ```
-    pub fn find_function<K: Into<Robj>>(&self, key: K) -> Option<Robj> {
-        let key = key.into();
-        if !self.is_environment() || !key.is_symbol() {
-            return None;
+    pub fn find_function<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
+        let key: Symbol = key.try_into()?;
+        if !self.is_environment() {
+            return Err(Error::NotFound(key.into()));
         }
         // This may be better:
         // let mut env: Robj = self.into();
@@ -139,9 +139,9 @@ impl Robj {
         // }
         unsafe {
             if let Ok(var) = catch_r_error(|| Rf_findFun(key.get(), self.get())) {
-                Some(new_owned(var))
+                Ok(new_owned(var))
             } else {
-                None
+                Err(Error::NotFound(key.into()))
             }
         }
     }
@@ -164,10 +164,10 @@ impl Robj {
     ///    //assert_eq!(global_env().find_var(sym!(imnotasymbol)), None);
     /// }
     /// ```
-    pub fn find_var<K: Into<Robj>>(&self, key: K) -> Option<Robj> {
-        let key = key.into();
-        if !self.is_environment() || !key.is_symbol() {
-            return None;
+    pub fn find_var<K: TryInto<Symbol, Error = Error>>(&self, key: K) -> Result<Robj> {
+        let key: Symbol = key.try_into()?;
+        if !self.is_environment() {
+            return Err(Error::NotFound(key.into()));
         }
         // Alterative:
         // let mut env: Robj = self.into();
@@ -188,12 +188,12 @@ impl Robj {
         unsafe {
             if let Ok(var) = catch_r_error(|| Rf_findVar(key.get(), self.get())) {
                 if var != R_UnboundValue {
-                    Some(new_owned(var))
+                    Ok(new_owned(var))
                 } else {
-                    None
+                    Err(Error::NotFound(key.into()))
                 }
             } else {
-                None
+                Err(Error::NotFound(key.into()))
             }
         }
     }
@@ -209,12 +209,7 @@ impl Robj {
     /// ```
     pub fn eval_promise(&self) -> Result<Robj> {
         if self.is_promise() {
-            let promise = self.as_promise().unwrap();
-            if !promise.value.is_unbound_value() {
-                Ok(promise.value)
-            } else {
-                self.eval()
-            }
+            self.as_promise().unwrap().eval()
         } else {
             Ok(self.into())
         }
@@ -230,12 +225,6 @@ impl Robj {
         unsafe { Rf_nrows(self.get()) as usize }
     }
 
-    #[doc(hidden)]
-    #[allow(non_snake_case)]
-    pub unsafe fn makeExternalPtr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
-        Robj::make_external_ptr(p, tag, prot)
-    }
-
     /// Internal function used to implement `#[extendr]` impl
     #[doc(hidden)]
     pub unsafe fn make_external_ptr<T>(p: *mut T, tag: Robj, prot: Robj) -> Self {
@@ -244,24 +233,12 @@ impl Robj {
         }))
     }
 
-    #[doc(hidden)]
-    #[allow(non_snake_case)]
-    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)]
-    #[allow(non_snake_case)]
-    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 {
@@ -274,12 +251,6 @@ impl Robj {
         new_owned(R_ExternalPtrProtected(self.get()))
     }
 
-    #[doc(hidden)]
-    #[allow(non_snake_case)]
-    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));
@@ -294,7 +265,7 @@ impl Robj {
                     new_owned(Rf_xlengthgets(self.get(), new_len as R_xlen_t))
                 }))
             } else {
-                Err(Error::NotAVectorType)
+                Err(Error::ExpectedVector(self.clone()))
             }
         }
     }
@@ -404,12 +375,22 @@ impl Robj {
         unsafe { Rf_isVectorizable(self.get()) != 0 }
     }
 
+    /// Return true if this is RAWSXP.
+    pub fn is_raw(&self) -> bool {
+        self.rtype() == RType::Raw
+    }
+
+    /// Return true if this is CHARSXP.
+    pub fn is_character(&self) -> bool {
+        self.rtype() == RType::Character
+    }
+
     /// 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() };
+            let tag = unsafe { self.external_ptr_tag() };
             if tag.as_str() == Some(expected_tag) {
                 return true;
             }
diff --git a/extendr-api/src/robj/try_from_robj.rs b/extendr-api/src/robj/try_from_robj.rs
new file mode 100644
index 000000000..b647bb307
--- /dev/null
+++ b/extendr-api/src/robj/try_from_robj.rs
@@ -0,0 +1,197 @@
+//! Conversions to Robj
+
+use super::*;
+
+macro_rules! impl_try_from_scalar {
+    ($t: ty) => {
+        impl TryFrom<Robj> for $t {
+            type Error = Error;
+
+            fn try_from(robj: Robj) -> Result<Self> {
+                if let Some(v) = robj.as_integer_slice() {
+                    match v.len() {
+                        0 => Err(Error::ExpectedNonZeroLength(robj)),
+                        1 => {
+                            if !v[0].is_na() {
+                                Ok(v[0] as Self)
+                            } else {
+                                Err(Error::MustNotBeNA(robj))
+                            }
+                        }
+                        _ => Err(Error::ExpectedScalar(robj)),
+                    }
+                } else if let Some(v) = robj.as_real_slice() {
+                    match v.len() {
+                        0 => Err(Error::ExpectedNonZeroLength(robj)),
+                        1 => {
+                            if !v[0].is_na() {
+                                Ok(v[0] as Self)
+                            } else {
+                                Err(Error::MustNotBeNA(robj))
+                            }
+                        }
+                        _ => Err(Error::ExpectedScalar(robj)),
+                    }
+                } else {
+                    Err(Error::ExpectedNumeric(robj))
+                }
+            }
+        }
+    };
+}
+
+impl_try_from_scalar!(u8);
+impl_try_from_scalar!(u16);
+impl_try_from_scalar!(u32);
+impl_try_from_scalar!(u64);
+impl_try_from_scalar!(i8);
+impl_try_from_scalar!(i16);
+impl_try_from_scalar!(i32);
+impl_try_from_scalar!(i64);
+impl_try_from_scalar!(f32);
+impl_try_from_scalar!(f64);
+
+impl TryFrom<Robj> for Bool {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(v) = robj.as_logical_slice() {
+            match v.len() {
+                0 => Err(Error::ExpectedNonZeroLength(robj)),
+                1 => Ok(v[0]),
+                _ => Err(Error::ExpectedScalar(robj)),
+            }
+        } else {
+            Err(Error::ExpectedLogical(robj))
+        }
+    }
+}
+
+impl TryFrom<Robj> for bool {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if robj.is_na() {
+            Err(Error::MustNotBeNA(robj))
+        } else {
+            Ok(<Bool>::try_from(robj)?.is_true())
+        }
+    }
+}
+
+impl TryFrom<Robj> for &str {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if robj.is_na() {
+            Err(Error::MustNotBeNA(robj))
+        } else if let Some(s) = robj.as_str() {
+            Ok(s)
+        } else {
+            Err(Error::ExpectedString(robj))
+        }
+    }
+}
+
+impl TryFrom<Robj> for String {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        <&str>::try_from(robj).map(|s| s.to_string())
+    }
+}
+
+impl TryFrom<Robj> for Vec<i32> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(v) = robj.as_integer_slice() {
+            Ok(Vec::from(v))
+        } else {
+            Err(Error::ExpectedInteger(robj))
+        }
+    }
+}
+
+impl TryFrom<Robj> for Vec<f64> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(v) = robj.as_real_slice() {
+            Ok(Vec::from(v))
+        } else {
+            Err(Error::ExpectedInteger(robj))
+        }
+    }
+}
+
+impl TryFrom<Robj> for Vec<String> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(iter) = robj.as_str_iter() {
+            // check for NA's in the string vector
+            if iter.clone().find(|&s| s.is_na()).is_some() {
+                Err(Error::MustNotBeNA(robj))
+            } else {
+                Ok(iter.map(|s| s.to_string()).collect::<Vec<String>>())
+            }
+        } else {
+            Err(Error::ExpectedString(robj))
+        }
+    }
+}
+
+macro_rules! impl_option {
+    ($type : ty) => {
+        impl TryFrom<Robj> for Option<$type> {
+            type Error = Error;
+
+            fn try_from(robj: Robj) -> Result<Self> {
+                if robj.is_na() {
+                    Ok(None)
+                } else {
+                    Ok(Some(<$type>::try_from(robj)?))
+                }
+            }
+        }
+    };
+}
+
+impl_option!(i32);
+impl_option!(f64);
+impl_option!(Bool);
+impl_option!(bool);
+impl_option!(&str);
+impl_option!(String);
+impl_option!(Vec<i32>);
+impl_option!(Vec<f64>);
+impl_option!(Vec<String>);
+
+impl TryFrom<Robj> for HashMap<String, Robj> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
+            Ok(iter
+                .map(|(k, v)| (k.to_string(), v.to_owned()))
+                .collect::<HashMap<String, Robj>>())
+        } else {
+            Err(Error::ExpectedList(robj))
+        }
+    }
+}
+
+impl TryFrom<Robj> for HashMap<&str, Robj> {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
+            Ok(iter
+                .map(|(k, v)| (k, v.to_owned()))
+                .collect::<HashMap<&str, Robj>>())
+        } else {
+            Err(Error::ExpectedList(robj))
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/character.rs b/extendr-api/src/wrapper/character.rs
new file mode 100644
index 000000000..69c7160a4
--- /dev/null
+++ b/extendr-api/src/wrapper/character.rs
@@ -0,0 +1,42 @@
+use super::*;
+
+/// Wrapper for creating character objects.
+/// These are used only as the contents of a character
+/// vector.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let chr = r!(Character::from_str("xyz"));
+///     assert_eq!(chr.as_character().unwrap().as_str(), "xyz");
+/// }
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct Character {
+    pub(crate) robj: Robj,
+}
+
+impl Character {
+    /// Make a character object from a string.
+    pub fn from_str(val: &str) -> Self {
+        unsafe {
+            Character {
+                robj: new_owned(str_to_character(val)),
+            }
+        }
+    }
+
+    /// Get the string from a character object.
+    /// If the string is NA, then the special na_str() is returned.
+    pub fn as_str(&self) -> &str {
+        unsafe {
+            let sexp = self.robj.get();
+            if sexp == R_NaString {
+                na_str()
+            } else {
+                to_str(R_CHAR(sexp) as *const u8)
+            }
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/environment.rs b/extendr-api/src/wrapper/environment.rs
new file mode 100644
index 000000000..2c81a5835
--- /dev/null
+++ b/extendr-api/src/wrapper/environment.rs
@@ -0,0 +1,245 @@
+use super::*;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Environment {
+    pub(crate) robj: Robj,
+}
+
+impl Environment {
+    /// Create a new, empty environment parented on global_env()
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let env = Environment::new(global_env());
+    ///     assert_eq!(env.len(), 0);
+    /// }
+    /// ```
+    pub fn new(parent: Environment) -> Self {
+        // 14 is a reasonable default.
+        Environment::new_with_capacity(parent, 14)
+    }
+
+    /// Create a new, empty environment parented on global_env()
+    /// with a reserved size.
+    ///
+    /// This function will guess the hash table size if required.
+    /// Use the Env{} wrapper for more detail.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let env = Environment::new_with_capacity(global_env(), 5);
+    ///     env.set_local(sym!(a), 1);
+    ///     env.set_local(sym!(b), 2);
+    ///     assert_eq!(env.len(), 2);
+    /// }
+    /// ```
+    pub fn new_with_capacity(parent: Environment, capacity: usize) -> Self {
+        let robj = if capacity <= 5 {
+            // Unhashed envirnment
+            call!("new.env", FALSE, parent, 0).unwrap()
+        } else {
+            // Hashed environment for larger hashmaps.
+            call!("new.env", TRUE, parent, capacity as i32 * 2 + 1).unwrap()
+        };
+        assert!(robj.is_environment());
+        Self { robj }
+    }
+
+    /// Make an R environment object.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// use std::convert::TryInto;
+    /// test! {
+    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
+    ///     let mut env = Environment::from_pairs(global_env(), names_and_values);
+    ///     assert_eq!(env.len(), 100);
+    /// }
+    /// ```
+    pub fn from_pairs<NV>(parent: Environment, names_and_values: NV) -> Self
+    where
+        NV: IntoIterator,
+        NV::Item: SymPair,
+    {
+        single_threaded(|| {
+            let dict_len = 29;
+            let robj = call!("new.env", TRUE, parent, dict_len).unwrap();
+            for nv in names_and_values {
+                let (n, v) = nv.sym_pair();
+                unsafe { Rf_defineVar(n.get(), v.get(), robj.get()) }
+            }
+            Environment { robj }
+        })
+    }
+
+    /// Get the enclosing (parent) environment.
+    pub fn parent(&self) -> Option<Environment> {
+        unsafe {
+            let sexp = self.robj.get();
+            let robj = new_owned(ENCLOS(sexp));
+            robj.try_into().ok()
+        }
+    }
+
+    /// Set the enclosing (parent) environment.
+    pub fn set_parent(&mut self, parent: Environment) -> &mut Self {
+        single_threaded(|| unsafe {
+            let sexp = self.robj.get();
+            SET_ENCLOS(sexp, parent.robj.get());
+        });
+        self
+    }
+
+    /// Get the environment flags.
+    pub fn envflags(&self) -> i32 {
+        unsafe {
+            let sexp = self.robj.get();
+            ENVFLAGS(sexp) as i32
+        }
+    }
+
+    /// Set the environment flags.
+    pub fn set_envflags(&mut self, flags: i32) -> &mut Self {
+        unsafe {
+            let sexp = self.robj.get();
+            SET_ENVFLAGS(sexp, flags.into())
+        }
+        self
+    }
+
+    /// Iterate over an environment.
+    pub fn iter(&self) -> EnvIter {
+        unsafe {
+            let hashtab = new_owned(HASHTAB(self.get()));
+            let frame = new_owned(FRAME(self.get()));
+            if hashtab.is_null() && frame.is_pairlist() {
+                EnvIter {
+                    hash_table: ListIter::new(),
+                    pairlist: frame.as_pairlist().unwrap().iter(),
+                }
+            } else {
+                EnvIter {
+                    hash_table: hashtab.as_list().unwrap().values(),
+                    pairlist: PairlistIter::new(),
+                }
+            }
+        }
+    }
+
+    /// Get the names in an environment.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///    let names_and_values : std::collections::HashMap<_, _> = (0..4).map(|i| (format!("n{}", i), r!(i))).collect();
+    ///    let env = Environment::from_pairs(global_env(), names_and_values);
+    ///    assert_eq!(env.names().collect::<Vec<_>>(), vec!["n0", "n1", "n2", "n3"]);
+    /// }
+    /// ```
+    pub fn names(&self) -> impl Iterator<Item = &str> {
+        self.iter().map(|(k, _)| k)
+    }
+
+    /// Set or define a variable in an enviroment.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let env = Environment::new(global_env());
+    ///     env.set_local(sym!(x), "harry");
+    ///     env.set_local(sym!(x), "fred");
+    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
+    /// }
+    /// ```
+    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
+        let key = key.into();
+        let value = value.into();
+        if key.is_symbol() {
+            single_threaded(|| unsafe {
+                Rf_defineVar(key.get(), value.get(), self.get());
+            })
+        }
+    }
+
+    /// Get a variable from an enviroment, but not its ancestors.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let env = Environment::new(global_env());
+    ///     env.set_local(sym!(x), "fred");
+    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
+    /// }
+    /// ```
+    pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
+        let key = key.into();
+        if key.is_symbol() {
+            unsafe { Ok(new_owned(Rf_findVarInFrame3(self.get(), key.get(), 1))) }
+        } else {
+            Err(Error::NotFound(key))
+        }
+    }
+}
+
+/// Iterator over the names and values of an environment
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
+///     let env = Environment::from_pairs(global_env(), names_and_values);
+///     let robj = r!(env);
+///     let names_and_values = robj.as_environment().unwrap().iter().collect::<Vec<_>>();
+///     assert_eq!(names_and_values.len(), 100);
+///
+///     let small_env = Environment::new_with_capacity(global_env(), 1);
+///     small_env.set_local(sym!(x), 1);
+///     let names_and_values = small_env.as_environment().unwrap().iter().collect::<Vec<_>>();
+///     assert_eq!(names_and_values, vec![("x", r!(1))]);
+///
+///     let large_env = Environment::new_with_capacity(global_env(), 1000);
+///     large_env.set_local(sym!(x), 1);
+///     let names_and_values = large_env.as_environment().unwrap().iter().collect::<Vec<_>>();
+///     assert_eq!(names_and_values, vec![("x", r!(1))]);
+/// }
+///
+/// ```
+#[derive(Clone)]
+pub struct EnvIter {
+    hash_table: ListIter,
+    pairlist: PairlistIter,
+}
+
+impl Iterator for EnvIter {
+    type Item = (&'static str, Robj);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            // Environments are a hash table (list) or pair lists (pairlist)
+            // Get the first available value from the pair list.
+            loop {
+                match self.pairlist.next() {
+                    Some((key, value)) => {
+                        // if the key and value are valid, return a pair.
+                        if !key.is_na() && !value.is_unbound_value() {
+                            return Some((key, value));
+                        }
+                    }
+                    // if the key and value are invalid, move on to the hash table.
+                    _ => break,
+                }
+                // continue pair list loop.
+            }
+
+            // Get the first pairlist from the hash table.
+            loop {
+                if let Some(obj) = self.hash_table.next() {
+                    if !obj.is_null() && obj.is_pairlist() {
+                        self.pairlist = obj.as_pairlist().unwrap().iter();
+                        break;
+                    }
+                // continue hash table loop.
+                } else {
+                    // The hash table is empty, end of iteration.
+                    return None;
+                }
+            }
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/expr.rs b/extendr-api/src/wrapper/expr.rs
new file mode 100644
index 000000000..433ef9086
--- /dev/null
+++ b/extendr-api/src/wrapper/expr.rs
@@ -0,0 +1,34 @@
+use super::*;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Expression {
+    pub(crate) robj: Robj,
+}
+
+impl Expression {
+    /// Wrapper for creating Expression (EXPRSXP) objects.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let expr = r!(Expression::from_values(&[r!(0), r!(1), r!(2)]));
+    ///     assert_eq!(expr.is_expression(), true);
+    ///     assert_eq!(expr.len(), 3);
+    ///     assert_eq!(format!("{:?}", expr), r#"r!(Expression::from_values([r!(0), r!(1), r!(2)]))"#);
+    /// }
+    /// ```
+    pub fn from_values<V>(values: V) -> Self
+    where
+        V: IntoIterator,
+        V::IntoIter: ExactSizeIterator,
+        V::Item: Into<Robj>,
+    {
+        Self {
+            robj: make_vector(EXPRSXP, values),
+        }
+    }
+
+    /// Return an iterator over the values of this expression list.
+    pub fn values(&self) -> ListIter {
+        ListIter::from_parts(self.robj.clone(), 0, self.robj.len())
+    }
+}
diff --git a/extendr-api/src/wrapper/function.rs b/extendr-api/src/wrapper/function.rs
index e71504954..056238e9c 100644
--- a/extendr-api/src/wrapper/function.rs
+++ b/extendr-api/src/wrapper/function.rs
@@ -5,80 +5,34 @@ use super::*;
 /// use extendr_api::prelude::*;
 /// test! {
 ///     let expr = R!(function(a = 1, b) {c <- a + b}).unwrap();
-///     let func = expr.as_func().unwrap();
+///     let func = expr.as_function().unwrap();
 ///
-///     let expected_formals = Pairlist {
-///         names_and_values: vec![("a", r!(1.0)), ("b", missing_arg())] };
+///     let expected_formals = Pairlist::from_pairs(vec![("a", r!(1.0)), ("b", missing_arg().into())]);
 ///     let expected_body = lang!(
 ///         "{", lang!("<-", sym!(c), lang!("+", sym!(a), sym!(b))));
 ///     assert_eq!(func.formals().as_pairlist().unwrap(), expected_formals);
 ///     assert_eq!(func.body(), expected_body);
-///     assert_eq!(func.env(), global_env());
+///     assert_eq!(func.environment(), global_env());
 /// }
 /// ```
 #[derive(Debug, PartialEq, Clone)]
 pub struct Function {
-    robj: Robj,
-}
-
-impl<'a> FromRobj<'a> for Function {
-    /// Convert an object that may be null to a rust type.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let s1 = r!(1);
-    ///     let n1 = <Nullable<i32>>::from_robj(&s1)?;
-    ///     assert_eq!(n1, Nullable::NotNull(1));
-    ///     let snull = r!(NULL);
-    ///     let nnull = <Nullable<i32>>::from_robj(&snull)?;
-    ///     assert_eq!(nnull, Nullable::Null);
-    /// }
-    /// ```
-    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if let Some(f) = robj.as_func() {
-            Ok(f)
-        } else {
-            Err("Not a function")
-        }
-    }
-}
-
-impl From<Function> for Robj {
-    /// Make an robj from a function wrapper.
-    /// The function wrapper is guaranteed to contain a function object.
-    fn from(val: Function) -> Self {
-        val.robj
-    }
+    pub(crate) robj: Robj,
 }
 
 impl Function {
-    /// Make a function from an Robj or return an error.
-    pub fn new(robj: Robj) -> Result<Self> {
-        if robj.is_function() {
-            Ok(Function { robj })
-        } else {
-            Err(Error::ExpectedFunction(robj))
-        }
-    }
-
     /// Make a function from parts.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let formals = pairlist!(a=NULL);
-    ///     let body = lang!("+", sym!(a), r!(1));
+    ///     let formals = pairlist!(a=NULL).try_into()?;
+    ///     let body = lang!("+", sym!(a), r!(1)).try_into()?;
     ///     let env = global_env();
     ///     let f = r!(Function::from_parts(formals, body, env )?);
     ///     assert_eq!(f.call(pairlist!(a=1))?, r!(2));
     /// }
     /// ```
-    pub fn from_parts(formals: Robj, body: Robj, env: Robj) -> Result<Self> {
-        if !formals.is_pairlist() {
-            return Err(Error::ExpectedPairlist(formals));
-        }
-        if !env.is_environment() {
-            return Err(Error::ExpectedEnviroment(env));
-        }
+    pub fn from_parts(formals: Pairlist, body: Language, env: Environment) -> Result<Self> {
         unsafe {
             let sexp = Rf_allocSExp(CLOSXP);
             let robj = new_owned(sexp);
@@ -93,7 +47,7 @@ impl Function {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let function = R!(function(a, b) a + b).unwrap().as_func().unwrap();
+    ///     let function = R!(function(a, b) a + b).unwrap().as_function().unwrap();
     ///     assert_eq!(function.call(pairlist!(a=1, b=2)).unwrap(), r!(3));
     /// }
     /// ```
@@ -102,10 +56,10 @@ impl Function {
     }
 
     /// Get the formal arguments of the function.
-    pub fn formals(&self) -> Robj {
+    pub fn formals(&self) -> Pairlist {
         unsafe {
             let sexp = self.robj.get();
-            new_owned(FORMALS(sexp))
+            new_owned(FORMALS(sexp)).try_into().unwrap()
         }
     }
 
@@ -118,10 +72,12 @@ impl Function {
     }
 
     /// Get the environment of the function.
-    pub fn env(&self) -> Robj {
+    pub fn environment(&self) -> Environment {
         unsafe {
             let sexp = self.robj.get();
             new_owned(CLOENV(sexp))
+                .try_into()
+                .expect("Should be an environment")
         }
     }
 }
diff --git a/extendr-api/src/wrapper/lang.rs b/extendr-api/src/wrapper/lang.rs
new file mode 100644
index 000000000..5d8513e08
--- /dev/null
+++ b/extendr-api/src/wrapper/lang.rs
@@ -0,0 +1,56 @@
+use super::*;
+
+/// Wrapper for creating language objects.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let call_to_xyz = r!(Language::from_values(&[sym!(xyz), r!(1), r!(2)]));
+///     assert_eq!(call_to_xyz.is_language(), true);
+///     assert_eq!(call_to_xyz.len(), 3);
+/// }
+/// ```
+///
+/// Note: You can use the [lang!] macro for this.
+#[derive(Debug, PartialEq, Clone)]
+pub struct Language {
+    pub(crate) robj: Robj,
+}
+
+impl Language {
+    pub fn from_values<T>(values: T) -> Self
+    where
+        T: IntoIterator,
+        T::IntoIter: DoubleEndedIterator,
+        T::Item: Into<Robj>,
+    {
+        single_threaded(|| unsafe {
+            let mut res = R_NilValue;
+            let mut num_protected = 0;
+            for val in values.into_iter().rev() {
+                let val = Rf_protect(val.into().get());
+                res = Rf_protect(Rf_lcons(val, res));
+                num_protected += 2;
+            }
+            let robj = new_owned(res);
+            Rf_unprotect(num_protected);
+            Language { robj }
+        })
+    }
+
+    pub fn iter(&self) -> PairlistIter {
+        unsafe {
+            PairlistIter {
+                robj: self.robj.clone(),
+                list_elem: self.robj.get(),
+            }
+        }
+    }
+
+    pub fn names(&self) -> impl Iterator<Item = &'static str> {
+        self.iter().map(|(tag, _)| tag)
+    }
+
+    pub fn values(&self) -> impl Iterator<Item = Robj> {
+        self.iter().map(|(_, robj)| robj)
+    }
+}
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
new file mode 100644
index 000000000..58bcdbb16
--- /dev/null
+++ b/extendr-api/src/wrapper/list.rs
@@ -0,0 +1,249 @@
+use super::*;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct List {
+    pub(crate) robj: Robj,
+}
+
+impl List {
+    /// Create a new, empty list.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let list = List::new();
+    ///     assert_eq!(list.is_list(), true);
+    ///     assert_eq!(list.len(), 0);
+    /// }
+    /// ```
+    pub fn new() -> List {
+        let values: &[Robj] = &[];
+        List::from_values(values)
+    }
+
+    /// Wrapper for creating list (VECSXP) objects.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let list = r!(List::from_values(&[r!(0), r!(1), r!(2)]));
+    ///     assert_eq!(list.is_list(), true);
+    ///     assert_eq!(list.len(), 3);
+    /// }
+    /// ```
+    pub fn from_values<V>(values: V) -> Self
+    where
+        V: IntoIterator,
+        V::IntoIter: ExactSizeIterator,
+        V::Item: Into<Robj>,
+    {
+        Self {
+            robj: make_vector(VECSXP, values),
+        }
+    }
+
+    pub fn from_pairs<V>(pairs: V) -> Self
+    where
+        V: IntoIterator,
+        V::IntoIter: ExactSizeIterator + Clone,
+        V::Item: Into<(&'static str, Robj)>,
+    {
+        let iter = pairs.into_iter();
+        let res = List::from_values(iter.clone().map(|kv| {
+            let (_, v) = kv.into();
+            v
+        }));
+        res.set_names(iter.map(|kv| {
+            let (k, _) = kv.into();
+            k
+        }))
+        .unwrap()
+        .as_list()
+        .unwrap()
+    }
+
+    /// Return an iterator over the values of this list.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let mut robj = list!(1, 2, 3);
+    ///     let objects : Vec<_> = robj.as_list().unwrap().values().collect();
+    ///     assert_eq!(objects, vec![r!(1), r!(2), r!(3)]);
+    /// }
+    /// ```
+    pub fn values(&self) -> ListIter {
+        ListIter::from_parts(self.robj.clone(), 0, self.robj.len())
+    }
+
+    /// Return an iterator over the names and values of this list.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let mut robj = list!(a=1, 2);
+    ///     let names_and_values : Vec<_> = robj.as_list().unwrap().iter().collect();
+    ///     assert_eq!(names_and_values, vec![("a", r!(1)), ("", r!(2))]);
+    /// }
+    /// ```
+    pub fn iter(&self) -> NamedListIter {
+        self.names()
+            .map(|n| n.zip(self.values()))
+            .unwrap_or_else(|| StrIter::new().zip(ListIter::new()))
+    }
+}
+
+impl IntoIterator for List {
+    type IntoIter = NamedListIter;
+    type Item = (&'static str, Robj);
+
+    /// Convert a List into an interator, consuming the list.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let list = list!(a=1, 2).as_list().unwrap();
+    ///     let vec : Vec<_> = list.into_iter().collect();
+    ///     assert_eq!(vec, vec![("a", r!(1)), ("", r!(2))]);
+    /// }
+    /// ```
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter().clone()
+    }
+}
+
+/// Iterator over the objects in a VECSXP, EXPRSXP or WEAKREFSXP.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let my_list = list!(a = 1, b = 2);
+///     let mut total = 0;
+///     for robj in my_list.as_list().unwrap().values() {
+///       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 {
+    robj: Robj,
+    i: usize,
+    len: usize,
+}
+
+impl ListIter {
+    // A new, empty list iterator.
+    pub fn new() -> Self {
+        ListIter::from_parts(().into(), 0, 0)
+    }
+
+    pub(crate) fn from_parts(robj: Robj, i: usize, len: usize) -> Self {
+        Self { robj, i, len }
+    }
+}
+
+impl Iterator for ListIter {
+    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(unsafe { new_owned(VECTOR_ELT(self.robj.get(), i as isize)) })
+        }
+    }
+
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.i += n;
+        self.next()
+    }
+}
+
+impl ExactSizeIterator for ListIter {
+    /// Length of a list iterator.
+    fn len(&self) -> usize {
+        self.len - self.i
+    }
+}
+
+/// You can use the FromList wrapper to coerce a Robj into a list.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let list = list!(1, 2);
+///     let vec : FromList<Vec<i32>> = list.try_into()?;
+///     assert_eq!(vec.0, vec![1, 2]);
+/// }
+/// ```
+pub struct FromList<T>(pub T);
+
+impl<T> TryFrom<Robj> for FromList<Vec<T>>
+where
+    T: TryFrom<Robj>,
+    <T as TryFrom<Robj>>::Error: Into<Error>,
+{
+    type Error = Error;
+
+    /// You can use the FromList wrapper to coerce a Robj into a list.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let list = list!(1, 2);
+    ///     let vec : FromList<Vec<i32>> = list.try_into()?;
+    ///     assert_eq!(vec.0, vec![1, 2]);
+    /// }
+    /// ```
+    fn try_from(robj: Robj) -> Result<Self> {
+        let listiter: ListIter = robj.try_into()?;
+        let res: Result<Vec<_>> = listiter
+            .map(|robj| T::try_from(robj).map_err(|e| e.into()))
+            .collect();
+        res.map(|v| FromList(v))
+    }
+}
+
+impl TryFrom<Robj> for ListIter {
+    type Error = Error;
+
+    /// You can pass a ListIter to a function.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let list = list!(1, 2);
+    ///     let vec : ListIter = list.try_into()?;
+    ///     assert_eq!(vec.collect::<Vec<_>>(), vec![r!(1), r!(2)]);
+    /// }
+    /// ```
+    fn try_from(robj: Robj) -> Result<Self> {
+        let list: List = robj.try_into()?;
+        Ok(list.values())
+    }
+}
+
+impl From<ListIter> for Robj {
+    /// You can return a ListIter from a function.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let listiter = list!(1, 2).as_list().unwrap().values();
+    ///     assert_eq!(Robj::from(listiter), list!(1, 2));
+    /// }
+    /// ```
+    fn from(iter: ListIter) -> Self {
+        iter.robj.clone()
+    }
+}
+
+impl<'a> FromRobj<'a> for ListIter {
+    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+        robj.as_list().map(|l| l.values()).ok_or("Not a list.")
+    }
+}
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
new file mode 100644
index 000000000..255af57f2
--- /dev/null
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -0,0 +1,402 @@
+//! Wrappers for matrices with deferred arithmetic.
+
+use super::*;
+use std::ops::{Index, IndexMut};
+
+/// Wrapper for creating and using matrices and arrays.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let matrix = RMatrix::new_matrix(3, 2, |r, c| [
+///         [1., 2., 3.],
+///          [4., 5., 6.]][c][r]);
+///     let robj = r!(matrix);
+///     assert_eq!(robj.is_matrix(), true);
+///     assert_eq!(robj.nrows(), 3);
+///     assert_eq!(robj.ncols(), 2);
+///
+///     let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("error")?;
+///     assert_eq!(matrix2.data().len(), 6);
+///     assert_eq!(matrix2.nrows(), 3);
+///     assert_eq!(matrix2.ncols(), 2);
+/// }
+/// ```
+#[derive(Debug, PartialEq)]
+pub struct RArray<T, D> {
+    /// Owning Robj (probably should be a Pin).
+    robj: Robj,
+
+    /// Slice of the data references the Robj.
+    data: *mut T,
+
+    /// Dimensions of the array.
+    dim: D,
+}
+
+pub type RColumn<T> = RArray<T, [usize; 1]>;
+pub type RMatrix<T> = RArray<T, [usize; 2]>;
+pub type RMatrix3D<T> = RArray<T, [usize; 3]>;
+
+const BASE: usize = 0;
+
+trait Offset<D> {
+    /// Get the offset into the array for a given index.
+    fn offset(&self, idx: D) -> usize;
+}
+
+impl<T> Offset<[usize; 1]> for RArray<T, [usize; 1]> {
+    /// Get the offset into the array for a given index.
+    fn offset(&self, index: [usize; 1]) -> usize {
+        if index[0] - BASE > self.dim[0] {
+            panic!("array index: row overflow");
+        }
+        index[0] - BASE
+    }
+}
+
+impl<T> Offset<[usize; 2]> for RArray<T, [usize; 2]> {
+    /// Get the offset into the array for a given index.
+    fn offset(&self, index: [usize; 2]) -> usize {
+        if index[0] - BASE > self.dim[0] {
+            panic!("matrix index: row overflow");
+        }
+        if index[1] - BASE > self.dim[1] {
+            panic!("matrix index: column overflow");
+        }
+        (index[0] - BASE) + self.dim[0] * (index[1] - BASE)
+    }
+}
+
+impl<T> Offset<[usize; 3]> for RArray<T, [usize; 3]> {
+    /// Get the offset into the array for a given index.
+    fn offset(&self, index: [usize; 3]) -> usize {
+        if index[0] - BASE > self.dim[0] {
+            panic!("RMatrix3D index: row overflow");
+        }
+        if index[1] - BASE > self.dim[1] {
+            panic!("RMatrix3D index: column overflow");
+        }
+        if index[2] - BASE > self.dim[2] {
+            panic!("RMatrix3D index: submatrix overflow");
+        }
+        (index[0] - BASE) + self.dim[0] * (index[1] - BASE + self.dim[1] * (index[2] - BASE))
+    }
+}
+
+impl<T, D> RArray<T, D> {
+    pub fn from_parts(robj: Robj, data: *mut T, dim: D) -> Self {
+        Self { robj, data, dim }
+    }
+
+    /// Get the underlying data fro this array.
+    pub fn data(&self) -> &[T] {
+        unsafe { std::slice::from_raw_parts(self.data, self.robj.len()) }
+    }
+
+    /// Get the dimensions for this array.
+    pub fn dim(&self) -> &D {
+        &self.dim
+    }
+}
+
+impl<'a, T: ToVectorValue + 'a> RColumn<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    /// Make a new column type.
+    pub fn new_column<F: FnMut(usize) -> T>(nrows: usize, mut f: F) -> Self {
+        let robj = (0..nrows).map(|r| f(r)).collect_robj();
+        let dim = [nrows];
+        let mut robj = robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
+        let slice = robj.as_typed_slice_mut().unwrap();
+        let data = slice.as_mut_ptr();
+        RArray::from_parts(robj, data, dim)
+    }
+
+    /// Get the number of rows.
+    pub fn nrows(&self) -> usize {
+        self.dim[0]
+    }
+}
+
+impl<'a, T: ToVectorValue + 'a> RMatrix<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    /// Create a new matrix wrapper.
+    /// Make a new column type.
+    pub fn new_matrix<F: Clone + FnMut(usize, usize) -> T>(
+        nrows: usize,
+        ncols: usize,
+        f: F,
+    ) -> Self {
+        let robj = (0..ncols)
+            .map(|c| {
+                let mut g = f.clone();
+                (0..nrows).map(move |r| g(r, c))
+            })
+            .flatten()
+            .collect_robj();
+        let dim = [nrows, ncols];
+        let mut robj = robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
+        let data = robj.as_typed_slice_mut().unwrap().as_mut_ptr();
+        RArray::from_parts(robj, data, dim)
+    }
+
+    /// Get the number of rows.
+    pub fn nrows(&self) -> usize {
+        self.dim[0]
+    }
+
+    /// Get the number of columns.
+    pub fn ncols(&self) -> usize {
+        self.dim[1]
+    }
+}
+
+impl<'a, T: ToVectorValue + 'a> RMatrix3D<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    pub fn new_matrix3d<F: Clone + FnMut(usize, usize, usize) -> T>(
+        nrows: usize,
+        ncols: usize,
+        nmatrix: usize,
+        f: F,
+    ) -> Self {
+        let robj = (0..nmatrix)
+            .map(|m| {
+                let h = f.clone();
+                (0..ncols)
+                    .map(move |c| {
+                        let mut g = h.clone();
+                        (0..nrows).map(move |r| g(r, c, m))
+                    })
+                    .flatten()
+            })
+            .flatten()
+            .collect_robj();
+        let dim = [nrows, ncols, nmatrix];
+        let mut robj = robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
+        let data = robj.as_typed_slice_mut().unwrap().as_mut_ptr();
+        RArray::from_parts(robj, data, dim)
+    }
+
+    /// Get the number of rows.
+    pub fn nrows(&self) -> usize {
+        self.dim[0]
+    }
+
+    /// Get the number of columns.
+    pub fn ncols(&self) -> usize {
+        self.dim[1]
+    }
+
+    /// Get the number of submatrices.
+    pub fn nsub(&self) -> usize {
+        self.dim[2]
+    }
+}
+
+impl<'a, T: 'a> TryFrom<Robj> for RColumn<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(mut robj: Robj) -> Result<Self> {
+        if let Some(slice) = robj.as_typed_slice_mut() {
+            Ok(RArray::from_parts(robj, slice.as_mut_ptr(), [slice.len()]))
+        } else {
+            Err(Error::ExpectedVector(robj))
+        }
+    }
+}
+
+impl<'a, T: 'a> TryFrom<Robj> for RMatrix<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(mut robj: Robj) -> Result<Self> {
+        if !robj.is_matrix() {
+            Err(Error::ExpectedMatrix(robj))
+        } else if let Some(slice) = robj.as_typed_slice_mut() {
+            if let Some(dim) = robj.dim() {
+                let dim: Vec<_> = dim.map(|d| d as usize).collect();
+                if dim.len() != 2 {
+                    Err(Error::ExpectedMatrix(robj))
+                } else {
+                    Ok(RArray::from_parts(
+                        robj,
+                        slice.as_mut_ptr(),
+                        [dim[0], dim[1]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix(robj))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj))
+        }
+    }
+}
+
+impl<'a, T: 'a> TryFrom<Robj> for RMatrix3D<T>
+where
+    Robj: AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(mut robj: Robj) -> Result<Self> {
+        if let Some(slice) = robj.as_typed_slice_mut() {
+            if let Some(dim) = robj.dim() {
+                if dim.len() != 3 {
+                    Err(Error::ExpectedMatrix3D(robj))
+                } else {
+                    let dim: Vec<_> = dim.map(|d| d as usize).collect();
+                    Ok(RArray::from_parts(
+                        robj,
+                        slice.as_mut_ptr(),
+                        [dim[0], dim[1], dim[2]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix3D(robj))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj))
+        }
+    }
+}
+
+impl<T, D> From<RArray<T, D>> for Robj {
+    /// Convert a column, matrix or matrix3d to an Robj.
+    fn from(array: RArray<T, D>) -> Self {
+        array.robj.clone()
+    }
+}
+
+impl Robj {
+    pub fn as_column<'a, E: 'a>(&self) -> Option<RColumn<E>>
+    where
+        Self: AsTypedSlice<'a, E>,
+    {
+        <RColumn<E>>::try_from(self.clone()).ok()
+    }
+
+    pub fn as_matrix<'a, E: 'a>(&self) -> Option<RMatrix<E>>
+    where
+        Self: AsTypedSlice<'a, E>,
+    {
+        <RMatrix<E>>::try_from(self.clone()).ok()
+    }
+
+    pub fn as_matrix3d<'a, E: 'a>(&self) -> Option<RMatrix3D<E>>
+    where
+        Self: AsTypedSlice<'a, E>,
+    {
+        <RMatrix3D<E>>::try_from(self.clone()).ok()
+    }
+}
+
+impl<T> Index<[usize; 2]> for RArray<T, [usize; 2]> {
+    type Output = T;
+
+    /// Zero-based indexing in row, column order.
+    ///
+    /// Panics if out of bounds.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///    let matrix = RArray::new_matrix(3, 2, |r, c| [
+    ///        [1., 2., 3.],
+    ///        [4., 5., 6.]][c][r]);
+    ///     assert_eq!(matrix[[0, 0]], 1.);
+    ///     assert_eq!(matrix[[1, 0]], 2.);
+    ///     assert_eq!(matrix[[2, 1]], 6.);
+    /// }
+    /// ```
+    fn index(&self, index: [usize; 2]) -> &Self::Output {
+        unsafe {
+            self.data
+                .offset(self.offset(index) as isize)
+                .as_ref()
+                .unwrap()
+        }
+    }
+}
+
+impl<T> IndexMut<[usize; 2]> for RArray<T, [usize; 2]> {
+    /// Zero-based mutable indexing in row, column order.
+    ///
+    /// Panics if out of bounds.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let mut matrix = RMatrix::new_matrix(3, 2, |_, _| 0.);
+    ///     matrix[[0, 0]] = 1.;
+    ///     matrix[[1, 0]] = 2.;
+    ///     matrix[[2, 0]] = 3.;
+    ///     matrix[[0, 1]] = 4.;
+    ///     assert_eq!(matrix.as_real_slice().unwrap(), &[1., 2., 3., 4., 0., 0.]);
+    /// }
+    /// ```
+    fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output {
+        unsafe {
+            self.data
+                .offset(self.offset(index) as isize)
+                .as_mut()
+                .unwrap()
+        }
+    }
+}
+
+impl<T, D> Deref for RArray<T, D> {
+    type Target = Robj;
+
+    fn deref(&self) -> &Self::Target {
+        &self.robj
+    }
+}
+
+#[test]
+fn matrix_ops() {
+    test! {
+        let vector = RColumn::new_column(3, |r| [1., 2., 3.][r]);
+        let robj = r!(vector);
+        assert_eq!(robj.is_vector(), true);
+        assert_eq!(robj.nrows(), 3);
+
+        let vector2 : RColumn<f64> = robj.as_column().ok_or("expected array")?;
+        assert_eq!(vector2.data().len(), 3);
+        assert_eq!(vector2.nrows(), 3);
+
+        let matrix = RMatrix::new_matrix(3, 2, |r, c| [
+            [1., 2., 3.],
+            [4., 5., 6.]][c][r]);
+        let robj = r!(matrix);
+        assert_eq!(robj.is_matrix(), true);
+        assert_eq!(robj.nrows(), 3);
+        assert_eq!(robj.ncols(), 2);
+        let matrix2 : RMatrix<f64> = robj.as_matrix().ok_or("expected matrix")?;
+        assert_eq!(matrix2.data().len(), 6);
+        assert_eq!(matrix2.nrows(), 3);
+        assert_eq!(matrix2.ncols(), 2);
+
+        let array = RMatrix3D::new_matrix3d(2, 2, 2, |r, c, m| [
+            [[1., 2.],  [3., 4.]],
+            [[5.,  6.], [7., 8.]]][m][c][r]);
+        let robj = r!(array);
+        assert_eq!(robj.is_array(), true);
+        assert_eq!(robj.nrows(), 2);
+        assert_eq!(robj.ncols(), 2);
+        let array2 : RMatrix3D<f64> = robj.as_matrix3d().ok_or("expected matrix3d")?;
+        assert_eq!(array2.data().len(), 8);
+        assert_eq!(array2.nrows(), 2);
+        assert_eq!(array2.ncols(), 2);
+        assert_eq!(array2.nsub(), 2);
+    }
+}
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 2afa50daf..2c4de3e00 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -1,355 +1,45 @@
 //! Wrappers are lightweight proxies for references to R datatypes.
 //! They do not contain an Robj (see array.rs for an example of this).
 
-#[doc(hidden)]
 use crate::*;
-#[doc(hidden)]
 use libR_sys::*;
 
-mod function;
-
+pub mod character;
+pub mod environment;
+pub mod expr;
+pub mod function;
+pub mod lang;
+pub mod list;
+pub mod matrix;
+pub mod nullable;
+pub mod pairlist;
+pub mod primitive;
+pub mod promise;
+pub mod raw;
+pub mod symbol;
+
+pub use character::Character;
+pub use environment::{EnvIter, Environment};
+pub use expr::Expression;
 pub use function::Function;
-
-/// Wrapper for creating symbols.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let symbol = r!(Symbol("xyz"));
-///     assert_eq!(symbol.as_symbol(), Some(Symbol("xyz")));
-///     assert!(symbol.is_symbol());
-/// }
-/// ```
-/// Note that creating a symbol from a string is expensive
-/// and so you may want to cache them.
-///
-#[derive(Debug, PartialEq, Clone)]
-pub struct Symbol<'a>(pub &'a str);
-
-/// Wrapper for creating character objects.
-/// These are used only as the contents of a character
-/// vector.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let chr = r!(Character("xyz"));
-///     assert_eq!(chr.as_character(), Some(Character("xyz")));
-/// }
-/// ```
-///
-#[derive(Debug, PartialEq, Clone)]
-pub struct Character<'a>(pub &'a str);
-
-/// Wrapper for creating raw (byte) objects.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let bytes = r!(Raw(&[1, 2, 3]));
-///     assert_eq!(bytes.len(), 3);
-///     assert_eq!(bytes.as_raw(), Some(Raw(&[1, 2, 3])));
-/// }
-/// ```
-///
-#[derive(Debug, PartialEq, Clone)]
-pub struct Raw<'a>(pub &'a [u8]);
-
-/// Wrapper for creating language objects.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let call_to_xyz = r!(Lang(&[r!(Symbol("xyz")), r!(1), r!(2)]));
-///     assert_eq!(call_to_xyz.is_language(), true);
-///     assert_eq!(call_to_xyz.len(), 3);
-/// }
-/// ```
-///
-/// Note: You can use the [lang!] macro for this.
-#[derive(Debug, PartialEq, Clone)]
-pub struct Lang<T>(pub T);
-
-/// Wrapper for creating pair list (LISTSXP) objects.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
-///     let expr = r!(Pairlist{names_and_values});
-///     assert_eq!(expr.len(), 100);
-/// }
-/// ```
-#[derive(Debug, PartialEq, Clone)]
-pub struct Pairlist<NV> {
-    pub names_and_values: NV,
-}
-
-/// Wrapper for creating list (VECSXP) objects.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let list = r!(List(&[r!(0), r!(1), r!(2)]));
-///     assert_eq!(list.is_list(), true);
-///     assert_eq!(list.len(), 3);
-///     assert_eq!(format!("{:?}", list), r#"r!(List([r!(0), r!(1), r!(2)]))"#);
-/// }
-/// ```
-///
-/// Note: you can use the [list!] macro for named lists.
-#[derive(Debug, PartialEq, Clone)]
-pub struct List<T>(pub T);
-
-/// Wrapper for creating expression objects.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let expr = r!(Expr(&[r!(1.), r!("xyz")]));
-///     assert_eq!(expr.len(), 2);
-/// }
-/// ```
-#[derive(Debug, PartialEq, Clone)]
-pub struct Expr<T>(pub T);
-
-/// Wrapper for creating environments.
-#[derive(Debug, PartialEq, Clone)]
-pub struct Env<P, NV> {
-    pub parent: P,
-    pub names_and_values: NV,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct Promise<C, E, V> {
-    pub code: C,
-    pub env: E,
-    pub value: V,
-    pub seen: bool,
-}
-
-/// Wrapper for creating and reading Primitive functions.
-///
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let robj = r!(Primitive("+"));
-///     assert!(robj.is_primitive());
-///     assert!(!r!(Primitive("not_a_primitive")).is_primitive());
-/// }
-/// ```
-#[derive(Debug, PartialEq, Clone)]
-pub struct Primitive<'a>(pub &'a str);
-
-/// Wrapper for handling potentially NULL values.
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     use extendr_api::wrapper::Nullable::*;
-///
-///     // Plain integer.
-///     let s1 = r!(1);
-///     let n1 = <Nullable<i32>>::from_robj(&s1)?;
-///     assert_eq!(n1, NotNull(1));
-///
-///     // NA integer - error.
-///     let sna = r!(NA_INTEGER);
-///     assert_eq!(<Nullable<i32>>::from_robj(&sna).is_err(), true);
-///
-///     // NA integer - option gives none.
-///     assert_eq!(<Nullable<Option<i32>>>::from_robj(&sna)?, NotNull(None));
-///
-///     // NULL object.
-///     let snull = r!(NULL);
-///     let nnull = <Nullable<i32>>::from_robj(&snull)?;
-///     assert_eq!(nnull, Null);
-///
-///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
-///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
-/// }
-/// ```
-#[derive(Debug, PartialEq, Clone)]
-pub enum Nullable<T> {
-    NotNull(T),
-    Null,
-}
-
-impl<T> From<List<T>> for Robj
-where
-    T: IntoIterator,
-    T::IntoIter: ExactSizeIterator,
-    T::Item: Into<Robj>,
-{
-    /// Make a list object from an iterator of Robjs.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let list_of_ints = r!(List(&[1, 2]));
-    ///     assert_eq!(list_of_ints.len(), 2);
-    /// }
-    /// ``````
-    fn from(val: List<T>) -> Self {
-        make_vector(VECSXP, val.0)
-    }
-}
-
-impl<T> From<Expr<T>> for Robj
-where
-    T: IntoIterator,
-    T::IntoIter: ExactSizeIterator,
-    T::Item: Into<Robj>,
-{
-    /// Make an expression object from an iterator of Robjs.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let list_of_ints = r!(Expr(&[1, 2]));
-    ///     assert_eq!(list_of_ints.len(), 2);
-    /// }
-    /// ```
-    fn from(val: Expr<T>) -> Self {
-        make_vector(EXPRSXP, val.0)
-    }
-}
-
-impl<'a> From<Raw<'a>> for Robj {
-    /// Make a raw object from bytes.
-    fn from(val: Raw<'a>) -> Self {
-        single_threaded(|| unsafe {
-            let val = val.0;
-            let sexp = Rf_allocVector(RAWSXP, val.len() as R_xlen_t);
-            ownership::protect(sexp);
-            let ptr = RAW(sexp);
-            for (i, &v) in val.iter().enumerate() {
-                *ptr.offset(i as isize) = v;
-            }
-            Robj::Owned(sexp)
-        })
-    }
-}
-
-impl<'a> From<Symbol<'a>> for Robj {
-    /// Make a symbol object.
-    fn from(name: Symbol) -> Self {
-        single_threaded(|| unsafe { new_owned(make_symbol(name.0)) })
-    }
-}
-
-impl<'a> From<Primitive<'a>> for Robj {
-    /// Make a primitive object, or NULL if not available.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let builtin = r!(Primitive("+"));
-    ///     let special = r!(Primitive("if"));
-    /// }
-    /// ```
-    fn from(name: Primitive) -> Self {
-        single_threaded(|| unsafe {
-            let sym = make_symbol(name.0);
-            let symvalue = new_sys(SYMVALUE(sym));
-            if symvalue.is_primitive() {
-                symvalue
-            } else {
-                r!(NULL)
-            }
-        })
-    }
-}
-
-impl<T> From<Lang<T>> for Robj
-where
-    T: IntoIterator,
-    T::IntoIter: DoubleEndedIterator,
-    T::Item: Into<Robj>,
-{
-    /// Convert a wrapper to an R language object.
-    fn from(val: Lang<T>) -> Self {
-        single_threaded(|| unsafe {
-            let mut res = R_NilValue;
-            let mut num_protected = 0;
-            for val in val.0.into_iter().rev() {
-                let val = Rf_protect(val.into().get());
-                res = Rf_protect(Rf_lcons(val, res));
-                num_protected += 2;
-            }
-            let res = new_owned(res);
-            Rf_unprotect(num_protected);
-            res
-        })
-    }
-}
-
-impl<'a, P, NV> From<Env<P, NV>> for Robj
-where
-    P: Into<Robj>,
-    NV: IntoIterator + 'a,
-    NV::Item: SymPair,
-{
-    /// Convert a wrapper to an R environment object.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
-    ///     let env = Env{parent: global_env(), names_and_values};
-    ///     let expr = r!(env);
-    ///     assert_eq!(expr.len(), 100);
-    /// }
-    /// ```
-    fn from(val: Env<P, NV>) -> Self {
-        single_threaded(|| {
-            let (parent, names_and_values) = (val.parent, val.names_and_values);
-            let dict_len = 29;
-            let res = call!("new.env", TRUE, parent.into(), dict_len).unwrap();
-            for nv in names_and_values {
-                let (n, v) = nv.sym_pair();
-                unsafe { Rf_defineVar(n.get(), v.get(), res.get()) }
-            }
-            res
-        })
-    }
-}
-
-impl<'a, NV> From<Pairlist<NV>> for Robj
-where
-    NV: IntoIterator + 'a,
-    NV::Item: SymPair,
-{
-    /// Convert a wrapper to a LISTSXP object.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
-    ///     let expr = r!(Pairlist{names_and_values});
-    ///     assert_eq!(expr.len(), 100);
-    /// }
-    /// ```
-    fn from(val: Pairlist<NV>) -> Self {
-        single_threaded(|| unsafe {
-            let names_and_values = val.names_and_values;
-            let mut num_protects = 0;
-            let mut res = R_NilValue;
-            let names_and_values: Vec<_> = names_and_values.into_iter().collect();
-            for nv in names_and_values.into_iter().rev() {
-                let (name, val) = nv.sym_pair();
-                let val = Rf_protect(val.get());
-                res = Rf_protect(Rf_cons(val, res));
-                num_protects += 2;
-                if !name.is_na() {
-                    SET_TAG(res, name.get());
-                }
-            }
-            let res = new_owned(res);
-            Rf_unprotect(num_protects as i32);
-            res
-        })
-    }
-}
-
-fn make_symbol(name: &str) -> SEXP {
+pub use lang::Language;
+pub use list::{FromList, List, ListIter};
+pub use matrix::{RArray, RColumn, RMatrix, RMatrix3D};
+pub use nullable::Nullable;
+pub use pairlist::{Pairlist, PairlistIter};
+pub use primitive::Primitive;
+pub use promise::Promise;
+pub use raw::Raw;
+pub use symbol::Symbol;
+
+pub(crate) fn make_symbol(name: &str) -> SEXP {
     let mut bytes = Vec::with_capacity(name.len() + 1);
     bytes.extend(name.bytes());
     bytes.push(0);
     unsafe { Rf_install(bytes.as_ptr() as *const i8) }
 }
 
-fn make_vector<T>(sexptype: u32, values: T) -> Robj
+pub(crate) fn make_vector<T>(sexptype: u32, values: T) -> Robj
 where
     T: IntoIterator,
     T::IntoIter: ExactSizeIterator,
@@ -366,88 +56,144 @@ where
     })
 }
 
-/// Allow you to skip the Symbol() in some cases.
-impl<'a> From<&'a str> for Symbol<'a> {
-    fn from(val: &'a str) -> Self {
-        Self(val)
-    }
+macro_rules! make_conversions {
+    ($typename: ident, $errname: ident, $isfunc: ident, $errstr: expr) => {
+        impl<'a> FromRobj<'a> for $typename {
+            fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+                if let Ok(f) = $typename::try_from(robj.clone()) {
+                    Ok(f)
+                } else {
+                    Err($errstr)
+                }
+            }
+        }
+
+        impl From<$typename> for Robj {
+            /// Make an robj from a wrapper.
+            fn from(val: $typename) -> Self {
+                val.robj
+            }
+        }
+
+        impl TryFrom<Robj> for $typename {
+            type Error = crate::Error;
+
+            /// Make a wrapper from a robj if it matches.
+            fn try_from(robj: Robj) -> Result<Self> {
+                if robj.$isfunc() {
+                    Ok($typename { robj })
+                } else {
+                    Err(Error::$errname(robj))
+                }
+            }
+        }
+
+        impl Deref for $typename {
+            type Target = Robj;
+
+            /// Make a wrapper behave like an Robj.
+            fn deref(&self) -> &Self::Target {
+                &self.robj
+            }
+        }
+    };
 }
 
+make_conversions!(Pairlist, ExpectedPairlist, is_pairlist, "Not a pairlist");
+make_conversions!(Function, ExpectedFunction, is_function, "Not a function");
+make_conversions!(Raw, ExpectedRaw, is_raw, "Not a raw object");
+
+make_conversions!(
+    Character,
+    ExpectedCharacter,
+    is_character,
+    "Not a character object"
+);
+
+make_conversions!(
+    Environment,
+    ExpectedEnviroment,
+    is_environment,
+    "Not an Environment"
+);
+
+make_conversions!(List, ExpectedList, is_list, "Not a List");
+
+make_conversions!(
+    Expression,
+    ExpectedExpression,
+    is_expression,
+    "Not an Expression"
+);
+
+make_conversions!(
+    Language,
+    ExpectedLanguage,
+    is_language,
+    "Not a Language object"
+);
+
+make_conversions!(Symbol, ExpectedSymbol, is_symbol, "Not a Symbol object");
+
+make_conversions!(
+    Primitive,
+    ExpectedPrimitive,
+    is_primitive,
+    "Not a Primitive object"
+);
+
+make_conversions!(Promise, ExpectedPromise, is_promise, "Not a Promise object");
+
 impl Robj {
     /// Convert a symbol object to a Symbol wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
     ///     let fred = sym!(fred);
-    ///     assert_eq!(fred.as_symbol(), Some(Symbol("fred")));
+    ///     assert_eq!(fred.as_symbol(), Some(Symbol::from_str("fred")));
     /// }
     /// ```
     pub fn as_symbol(&self) -> Option<Symbol> {
-        if self.is_symbol() {
-            unsafe {
-                let printname = PRINTNAME(self.get());
-                if TYPEOF(printname) as u32 == CHARSXP {
-                    Some(Symbol(to_str(R_CHAR(printname) as *const u8)))
-                } else {
-                    // This should never occur.
-                    None
-                }
-            }
-        } else {
-            None
-        }
+        Symbol::try_from(self.clone()).ok()
     }
 
     /// Convert a character object to a Character wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let fred = r!(Character("fred"));
-    ///     assert_eq!(fred.as_character(), Some(Character("fred")));
+    ///     let fred = r!(Character::from_str("fred"));
+    ///     assert_eq!(fred.as_character(), Some(Character::from_str("fred")));
     /// }
     /// ```
     pub fn as_character(&self) -> Option<Character> {
-        if self.sexptype() == CHARSXP {
-            Some(Character(unsafe {
-                to_str(R_CHAR(self.get()) as *const u8)
-            }))
-        } else {
-            None
-        }
+        Character::try_from(self.clone()).ok()
     }
 
     /// Convert a raw object to a Character wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let bytes = r!(Raw(&[1, 2, 3]));
+    ///     let bytes = r!(Raw::from_bytes(&[1, 2, 3]));
     ///     assert_eq!(bytes.len(), 3);
-    ///     assert_eq!(bytes.as_raw(), Some(Raw(&[1, 2, 3])));
+    ///     assert_eq!(bytes.as_raw(), Some(Raw::from_bytes(&[1, 2, 3])));
     /// }
     /// ```
     pub fn as_raw(&self) -> Option<Raw> {
-        if self.sexptype() == RAWSXP {
-            Some(Raw(self.as_raw_slice().unwrap()))
-        } else {
-            None
-        }
+        Raw::try_from(self.clone()).ok()
     }
-    /// Convert a language object to a Lang wrapper.
+
+    /// Convert a language object to a Language wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let call_to_xyz = r!(Lang(&[r!(Symbol("xyz")), r!(1), r!(2)]));
+    ///     let call_to_xyz = r!(Language::from_values(&[r!(Symbol::from_str("xyz")), r!(1), r!(2)]));
     ///     assert_eq!(call_to_xyz.is_language(), true);
     ///     assert_eq!(call_to_xyz.len(), 3);
-    ///     assert_eq!(format!("{:?}", call_to_xyz), r#"r!(Lang([sym!(xyz), r!(1), r!(2)]))"#);
+    ///     assert_eq!(format!("{:?}", call_to_xyz), r#"r!(Language::from_values([sym!(xyz), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_lang(&self) -> Option<Lang<PairlistIter>> {
-        if self.sexptype() == LANGSXP {
-            Some(Lang(self.as_pairlist_iter().unwrap()))
-        } else {
-            None
-        }
+    pub fn as_language(&self) -> Option<Language> {
+        Language::try_from(self.clone()).ok()
     }
 
     /// Convert a pair list object (LISTSXP) to a Pairlist wrapper.
@@ -455,79 +201,56 @@ impl Robj {
     /// use extendr_api::prelude::*;
     /// test! {
     ///     let names_and_values = vec![("a", r!(1)), ("b", r!(2)), (na_str(), r!(3))];
-    ///     let pairlist = Pairlist{ names_and_values };
+    ///     let pairlist = Pairlist::from_pairs(names_and_values);
     ///     let robj = r!(pairlist.clone());
     ///     assert_eq!(robj.as_pairlist().unwrap(), pairlist);
     /// }
     /// ```
-    pub fn as_pairlist(&self) -> Option<Pairlist<Vec<(&str, Robj)>>> {
-        if self.sexptype() == LISTSXP {
-            let names = self.as_pairlist_tag_iter().unwrap();
-            let values = self.as_pairlist_iter().unwrap();
-            let names_and_values: Vec<_> = names.zip(values).collect();
-            Some(Pairlist { names_and_values })
-        } else {
-            None
-        }
+    pub fn as_pairlist(&self) -> Option<Pairlist> {
+        Pairlist::try_from(self.clone()).ok()
     }
 
     /// Convert a list object (VECSXP) to a List wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let list = r!(List(&[r!(0), r!(1), r!(2)]));
+    ///     let list = r!(List::from_values(&[r!(0), r!(1), r!(2)]));
     ///     assert_eq!(list.is_list(), true);
-    ///     assert_eq!(format!("{:?}", list), r#"r!(List([r!(0), r!(1), r!(2)]))"#);
+    ///     assert_eq!(format!("{:?}", list), r#"r!(List::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_list(&self) -> Option<List<ListIter>> {
-        self.as_list_iter().map(|l| List(l))
+    pub fn as_list(&self) -> Option<List> {
+        List::try_from(self.clone()).ok()
     }
 
     /// Convert an expression object (EXPRSXP) to a Expr wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let expr = r!(Expr(&[r!(0), r!(1), r!(2)]));
-    ///     assert_eq!(expr.is_expr(), true);
-    ///     assert_eq!(expr.as_expr(), Some(Expr(vec![r!(0), r!(1), r!(2)])));
-    ///     assert_eq!(format!("{:?}", expr), r#"r!(Expr([r!(0), r!(1), r!(2)]))"#);
+    ///     let expr = r!(Expression::from_values(&[r!(0), r!(1), r!(2)]));
+    ///     assert_eq!(expr.is_expression(), true);
+    ///     assert_eq!(expr.as_expression(), Some(Expression::from_values(vec![r!(0), r!(1), r!(2)])));
+    ///     assert_eq!(format!("{:?}", expr), r#"r!(Expression::from_values([r!(0), r!(1), r!(2)]))"#);
     /// }
     /// ```
-    pub fn as_expr(&self) -> Option<Expr<Vec<Robj>>> {
-        if self.sexptype() == EXPRSXP {
-            let res: Vec<_> = self
-                .as_list_iter()
-                .unwrap()
-                .map(|robj| robj.to_owned())
-                .collect();
-            Some(Expr(res))
-        } else {
-            None
-        }
+    pub fn as_expression(&self) -> Option<Expression> {
+        Expression::try_from(self.clone()).ok()
     }
 
     /// Convert an environment object (ENVSXP) to a Env wrapper.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), r!(i)));
-    ///     let env = Env{parent: global_env(), names_and_values};
-    ///     let expr = r!(env.clone());
+    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
+    ///     let env = Environment::from_pairs(global_env(), names_and_values);
+    ///     let expr = env.clone();
     ///     assert_eq!(expr.len(), 100);
     ///     let env2 = expr.as_environment().unwrap();
-    ///     assert_eq!(env2.names_and_values.count(), 100);
+    ///     assert_eq!(env2.len(), 100);
     /// }
     /// ```
-    pub fn as_environment(&self) -> Option<Env<Robj, EnvIter>> {
-        if self.is_environment() {
-            Some(Env {
-                parent: self.parent().unwrap(),
-                names_and_values: self.as_env_iter().unwrap(),
-            })
-        } else {
-            None
-        }
+    pub fn as_environment(&self) -> Option<Environment> {
+        Environment::try_from(self.clone()).ok()
     }
 
     /// Convert a function object (CLOSXP) to a Function wrapper.
@@ -535,53 +258,16 @@ impl Robj {
     /// use extendr_api::prelude::*;
     /// test! {
     ///     let func = R!(function(a,b) a + b).unwrap();
-    ///     println!("{:?}", func.as_func());
+    ///     println!("{:?}", func.as_function());
     /// }
     /// ```
-    pub fn as_func(&self) -> Option<Function> {
-        if let Ok(f) = Function::new(self.clone()) {
-            Some(f)
-        } else {
-            None
-        }
+    pub fn as_function(&self) -> Option<Function> {
+        Function::try_from(self.clone()).ok()
     }
 
-    // /// Convert a primitive object (BUILTINSXP or SPECIALSXP) to a wrapper.
-    // /// ```
-    // /// use extendr_api::prelude::*;
-    // /// test! {
-    // ///  let builtin = r!(Primitive("+"));
-    // ///  let special = r!(Primitive("if"));
-    // ///  assert_eq!(builtin.sexptype(), libR_sys::BUILTINSXP);
-    // ///  assert_eq!(special.sexptype(), libR_sys::SPECIALSXP);
-    // /// }
-    // /// ```
-    // pub fn as_primitive(&self) -> Option<Primitive> {
-    //     match self.sexptype() {
-    //         BUILTINSXP | SPECIALSXP => {
-    //             // Unfortunately, for now PRIMNAME is out of bounds.
-    //             //Some(Primitive(unsafe {to_str(PRIMNAME(self.get()) as * const u8)}))
-    //             None
-    //         }
-    //         _ => None,
-    //     }
-    // }
-
     /// Get a wrapper for a promise.
-    pub fn as_promise(&self) -> Option<Promise<Robj, Robj, Robj>> {
-        if self.is_promise() {
-            unsafe {
-                let sexp = self.get();
-                Some(Promise {
-                    code: new_owned(PRCODE(sexp)),
-                    env: new_owned(PRENV(sexp)),
-                    value: new_owned(PRVALUE(sexp)),
-                    seen: PRSEEN(sexp) != 0,
-                })
-            }
-        } else {
-            None
-        }
+    pub fn as_promise(&self) -> Option<Promise> {
+        Promise::try_from(self.clone()).ok()
     }
 }
 
@@ -595,51 +281,17 @@ where
     R: Into<Robj>,
 {
     fn sym_pair(self) -> (Robj, Robj) {
-        (r!(Symbol(self.0.as_ref())), self.1.into())
+        (r!(Symbol::from_str(self.0.as_ref())), self.1.into())
     }
 }
 
-impl<'a, T> FromRobj<'a> for Nullable<T>
+impl<S, R> SymPair for &(S, R)
 where
-    T: FromRobj<'a>,
-{
-    /// Convert an object that may be null to a rust type.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     let s1 = r!(1);
-    ///     let n1 = <Nullable<i32>>::from_robj(&s1)?;
-    ///     assert_eq!(n1, Nullable::NotNull(1));
-    ///     let snull = r!(NULL);
-    ///     let nnull = <Nullable<i32>>::from_robj(&snull)?;
-    ///     assert_eq!(nnull, Nullable::Null);
-    /// }
-    /// ```
-    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-        if robj.is_null() {
-            Ok(Nullable::Null)
-        } else {
-            Ok(Nullable::NotNull(<T>::from_robj(robj)?))
-        }
-    }
-}
-
-impl<T> From<Nullable<T>> for Robj
-where
-    T: Into<Robj>,
+    S: AsRef<str>,
+    R: Into<Robj>,
+    R: Clone,
 {
-    /// Convert a rust object to NULL or another type.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
-    ///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
-    /// }
-    /// ```
-    fn from(val: Nullable<T>) -> Self {
-        match val {
-            Nullable::NotNull(t) => t.into(),
-            Nullable::Null => r!(NULL),
-        }
+    fn sym_pair(self) -> (Robj, Robj) {
+        (r!(Symbol::from_str(self.0.as_ref())), self.1.clone().into())
     }
 }
diff --git a/extendr-api/src/wrapper/nullable.rs b/extendr-api/src/wrapper/nullable.rs
new file mode 100644
index 000000000..7a4e4f651
--- /dev/null
+++ b/extendr-api/src/wrapper/nullable.rs
@@ -0,0 +1,79 @@
+use super::*;
+
+/// Wrapper for handling potentially NULL values.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     use extendr_api::wrapper::Nullable::*;
+///
+///     // Plain integer.
+///     let s1 = r!(1);
+///     let n1 = <Nullable<i32>>::from_robj(&s1)?;
+///     assert_eq!(n1, NotNull(1));
+///
+///     // NA integer - error.
+///     let sna = r!(NA_INTEGER);
+///     assert_eq!(<Nullable<i32>>::from_robj(&sna).is_err(), true);
+///
+///     // NA integer - option gives none.
+///     assert_eq!(<Nullable<Option<i32>>>::from_robj(&sna)?, NotNull(None));
+///
+///     // NULL object.
+///     let snull = r!(NULL);
+///     let nnull = <Nullable<i32>>::from_robj(&snull)?;
+///     assert_eq!(nnull, Null);
+///
+///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
+///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
+/// }
+/// ```
+#[derive(Debug, PartialEq, Clone)]
+pub enum Nullable<T> {
+    NotNull(T),
+    Null,
+}
+
+impl<'a, T> FromRobj<'a> for Nullable<T>
+where
+    T: FromRobj<'a>,
+{
+    /// Convert an object that may be null to a rust type.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let s1 = r!(1);
+    ///     let n1 = <Nullable<i32>>::from_robj(&s1)?;
+    ///     assert_eq!(n1, Nullable::NotNull(1));
+    ///     let snull = r!(NULL);
+    ///     let nnull = <Nullable<i32>>::from_robj(&snull)?;
+    ///     assert_eq!(nnull, Nullable::Null);
+    /// }
+    /// ```
+    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+        if robj.is_null() {
+            Ok(Nullable::Null)
+        } else {
+            Ok(Nullable::NotNull(<T>::from_robj(robj)?))
+        }
+    }
+}
+
+impl<T> From<Nullable<T>> for Robj
+where
+    T: Into<Robj>,
+{
+    /// Convert a rust object to NULL or another type.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
+    ///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
+    /// }
+    /// ```
+    fn from(val: Nullable<T>) -> Self {
+        match val {
+            Nullable::NotNull(t) => t.into(),
+            Nullable::Null => r!(NULL),
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/pairlist.rs b/extendr-api/src/wrapper/pairlist.rs
new file mode 100644
index 000000000..d67615c39
--- /dev/null
+++ b/extendr-api/src/wrapper/pairlist.rs
@@ -0,0 +1,169 @@
+//! A pairlist is a linked list of values with optional symbol tags.
+
+use super::*;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Pairlist {
+    pub(crate) robj: Robj,
+}
+
+impl Pairlist {
+    /// Convert an iterator of names and values to a pairlist object.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
+    ///     let pairlist = Pairlist::from_pairs(pairs);
+    ///     assert_eq!(pairlist.len(), 100);
+    /// }
+    /// ```
+    pub fn from_pairs<NV>(pairs: NV) -> Self
+    where
+        NV: IntoIterator,
+        NV::IntoIter: DoubleEndedIterator,
+        NV::Item: SymPair,
+    {
+        crate::single_threaded(|| unsafe {
+            let mut num_protects = 0;
+            let mut res = R_NilValue;
+            for nv in pairs.into_iter().rev() {
+                let (name, val) = nv.sym_pair();
+                let val = Rf_protect(val.get());
+                res = Rf_protect(Rf_cons(val, res));
+                num_protects += 2;
+                if !name.is_unbound_value() {
+                    SET_TAG(res, name.get());
+                }
+            }
+            let res = Pairlist {
+                robj: new_owned(res),
+            };
+            Rf_unprotect(num_protects as i32);
+            res
+        })
+    }
+
+    /// Generate paits of names and values.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
+    ///     let pairlist = Pairlist::from_pairs(pairs);
+    ///     assert_eq!(pairlist.iter().count(), 100);
+    ///     assert_eq!(pairlist.iter().nth(50), Some(("n50", r!(50))));
+    /// }
+    /// ```
+    pub fn iter(&self) -> PairlistIter {
+        unsafe {
+            PairlistIter {
+                robj: self.robj.clone(),
+                list_elem: self.robj.get(),
+            }
+        }
+    }
+
+    pub fn names(&self) -> impl Iterator<Item = &'static str> {
+        self.iter().map(|(tag, _)| tag)
+    }
+
+    pub fn values(&self) -> impl Iterator<Item = Robj> {
+        self.iter().map(|(_, robj)| robj)
+    }
+}
+
+/// Generate paits of names and values.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
+///     let pairlist = Pairlist::from_pairs(pairs);
+///     assert_eq!(pairlist.iter().count(), 100);
+///     assert_eq!(pairlist.iter().nth(50), Some(("n50", r!(50))));
+/// }
+/// ```
+#[derive(Clone)]
+pub struct PairlistIter {
+    pub(crate) robj: Robj,
+    pub(crate) list_elem: SEXP,
+}
+
+impl PairlistIter {
+    /// Make an empty pairlist iterator.
+    pub fn new() -> Self {
+        unsafe {
+            Self {
+                robj: ().into(),
+                list_elem: R_NilValue,
+            }
+        }
+    }
+}
+
+impl Iterator for PairlistIter {
+    // Note: The static is bad here, but we await RFC 1598
+    // to do this properly. Howevere, symbols live forever.
+    // https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
+    type Item = (&'static str, Robj);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let sexp = self.list_elem;
+            if sexp == R_NilValue {
+                None
+            } else {
+                let tag = TAG(sexp);
+                let value = new_owned(CAR(sexp));
+                self.list_elem = CDR(sexp);
+                if TYPEOF(tag) == SYMSXP as i32 {
+                    let printname = PRINTNAME(tag);
+                    assert!(TYPEOF(printname) as u32 == CHARSXP);
+                    let name = to_str(R_CHAR(printname) as *const u8);
+                    Some((std::mem::transmute(name), value))
+                } else {
+                    Some((na_str(), value))
+                }
+            }
+        }
+    }
+}
+
+impl IntoIterator for Pairlist {
+    type IntoIter = PairlistIter;
+    type Item = (&'static str, Robj);
+
+    /// Convert a PairList into an interator, consuming the pairlist.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let pairlist = pairlist!(a=1, 2).as_pairlist().unwrap();
+    ///     let vec : Vec<_> = pairlist.into_iter().collect();
+    ///     assert_eq!(vec, vec![("a", r!(1)), (na_str(), r!(2))]);
+    /// }
+    /// ```
+    fn into_iter(self) -> Self::IntoIter {
+        unsafe {
+            let sexp = self.robj.get();
+            PairlistIter {
+                robj: self.robj,
+                list_elem: sexp,
+            }
+        }
+    }
+}
+
+impl TryFrom<Robj> for PairlistIter {
+    type Error = Error;
+
+    /// You can pass a PairlistIter to a function.
+    fn try_from(robj: Robj) -> Result<Self> {
+        let pairlist: Pairlist = robj.try_into()?;
+        Ok(pairlist.into_iter())
+    }
+}
+
+impl From<PairlistIter> for Robj {
+    /// You can return a PairlistIter from a function.
+    fn from(iter: PairlistIter) -> Self {
+        iter.robj.clone()
+    }
+}
diff --git a/extendr-api/src/wrapper/primitive.rs b/extendr-api/src/wrapper/primitive.rs
new file mode 100644
index 000000000..f66daa2a2
--- /dev/null
+++ b/extendr-api/src/wrapper/primitive.rs
@@ -0,0 +1,44 @@
+use super::*;
+
+/// Wrapper for creating primitive objects.
+///
+/// Make a primitive object, or NULL if not available.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let builtin = r!(Primitive::from_str("+"));
+///     let special = r!(Primitive::from_str("if"));
+/// }
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct Primitive {
+    pub(crate) robj: Robj,
+}
+
+impl Primitive {
+    /// Make a Primitive object from a string.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let builtin = r!(Primitive::from_str("+")?);
+    ///     let special = r!(Primitive::from_str("if")?);
+    ///     assert_eq!(builtin.rtype(), RType::Builtin);
+    ///     assert_eq!(special.rtype(), RType::Special);
+    /// }
+    /// ```
+    pub fn from_str(val: &str) -> Result<Self> {
+        single_threaded(|| unsafe {
+            // Primitives have a special "SYMVALUE" entry in their symbol.
+            let sym = Symbol::from_str(val);
+            let symvalue = new_owned(SYMVALUE(sym.get()));
+            if symvalue.is_primitive() {
+                Ok(Primitive { robj: symvalue })
+            } else {
+                Err(Error::ExpectedPrimitive(sym.into()))
+            }
+        })
+    }
+
+    // There is currently no way to convert a primitive to a string.
+}
diff --git a/extendr-api/src/wrapper/promise.rs b/extendr-api/src/wrapper/promise.rs
new file mode 100644
index 000000000..34c64ec16
--- /dev/null
+++ b/extendr-api/src/wrapper/promise.rs
@@ -0,0 +1,81 @@
+use super::*;
+
+/// Wrapper for creating promises (PROMSXP).
+#[derive(Debug, PartialEq, Clone)]
+pub struct Promise {
+    pub(crate) robj: Robj,
+}
+
+impl Promise {
+    /// Make a Promise from parts.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let promise = Promise::from_parts(r!(1), global_env())?;
+    ///     assert!(promise.value().is_unbound_value());
+    ///     assert_eq!(promise.eval_promise()?, r!(1));
+    ///     assert_eq!(promise.value(), r!(1));
+    /// }
+    /// ```
+    pub fn from_parts(code: Robj, environment: Environment) -> Result<Self> {
+        unsafe {
+            let sexp = Rf_allocSExp(PROMSXP);
+            let robj = new_owned(sexp);
+            SET_PRCODE(sexp, code.get());
+            SET_PRENV(sexp, environment.robj.get());
+            SET_PRVALUE(sexp, R_UnboundValue);
+            SET_PRSEEN(sexp, 0);
+            Ok(Promise { robj })
+        }
+    }
+
+    /// Get the code to be executed from the promise.
+    pub fn code(&self) -> Robj {
+        unsafe {
+            let sexp = self.robj.get();
+            new_owned(PRCODE(sexp))
+        }
+    }
+
+    /// Get the environment for the execution from the promise.
+    pub fn environment(&self) -> Environment {
+        unsafe {
+            let sexp = self.robj.get();
+            new_owned(PRENV(sexp)).try_into().unwrap()
+        }
+    }
+
+    /// Get the value of the promise, once executed.
+    pub fn value(&self) -> Robj {
+        unsafe {
+            let sexp = self.robj.get();
+            new_owned(PRVALUE(sexp))
+        }
+    }
+
+    /// Get the seen flag (avoids recursion).
+    pub fn seen(&self) -> i32 {
+        unsafe {
+            let sexp = self.robj.get();
+            PRSEEN(sexp)
+        }
+    }
+
+    /// If this promise has not been evaluated, evaluate it, otherwise return the value.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///    let iris_promise = global_env().find_var(sym!(iris)).unwrap();
+    ///    let iris_dataframe = iris_promise.as_promise().unwrap().eval().unwrap();
+    ///    assert_eq!(iris_dataframe.is_frame(), true);
+    /// }
+    /// ```
+    pub fn eval(&self) -> Result<Robj> {
+        assert!(self.is_promise());
+        if !self.value().is_unbound_value() {
+            Ok(self.value())
+        } else {
+            self.robj.eval()
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/raw.rs b/extendr-api/src/wrapper/raw.rs
new file mode 100644
index 000000000..c42f0ecda
--- /dev/null
+++ b/extendr-api/src/wrapper/raw.rs
@@ -0,0 +1,44 @@
+use super::*;
+
+/// Wrapper for creating raw (byte) objects.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let bytes = r!(Raw::from_bytes(&[1, 2, 3]));
+///     assert_eq!(bytes.len(), 3);
+///     assert_eq!(bytes.as_raw(), Some(Raw::from_bytes(&[1, 2, 3])));
+/// }
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct Raw {
+    pub(crate) robj: Robj,
+}
+
+impl Raw {
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let bytes = r!(Raw::from_bytes(&[1, 2, 3]));
+    ///     assert_eq!(bytes.len(), 3);
+    ///     assert_eq!(bytes.as_raw(), Some(Raw::from_bytes(&[1, 2, 3])));
+    /// }
+    /// ```
+    pub fn from_bytes(bytes: &[u8]) -> Self {
+        single_threaded(|| unsafe {
+            let sexp = Rf_allocVector(RAWSXP, bytes.len() as R_xlen_t);
+            let robj = new_owned(sexp);
+            let ptr = RAW(sexp);
+            for (i, &v) in bytes.into_iter().enumerate() {
+                *ptr.offset(i as isize) = v;
+            }
+            Raw { robj }
+        })
+    }
+
+    /// Get a slice of bytes from the Raw object.
+    pub fn as_slice(&self) -> &[u8] {
+        self.robj.as_typed_slice().unwrap()
+    }
+}
diff --git a/extendr-api/src/wrapper/symbol.rs b/extendr-api/src/wrapper/symbol.rs
new file mode 100644
index 000000000..10d7f5235
--- /dev/null
+++ b/extendr-api/src/wrapper/symbol.rs
@@ -0,0 +1,270 @@
+use super::*;
+
+/// Wrapper for creating symbol objects.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let chr = r!(Symbol::from_str("xyz"));
+///     assert_eq!(chr.as_symbol().unwrap().as_str(), "xyz");
+/// }
+/// ```
+///
+#[derive(Debug, PartialEq, Clone)]
+pub struct Symbol {
+    pub(crate) robj: Robj,
+}
+
+impl Symbol {
+    /// Make a symbol object from a string.
+    ///
+    /// The values na_str() and "" give unbound_value().
+    ///
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     let chr = r!(Symbol::from_str("xyz"));
+    ///     assert_eq!(chr, sym!(xyz));
+    ///     assert_eq!(Symbol::from_str(na_str()).is_unbound_value(), true);
+    ///     assert_eq!(Symbol::from_str("").is_unbound_value(), true);
+    /// }
+    /// ```
+    pub fn from_str(val: &str) -> Self {
+        if val == "" || val.is_na() {
+            unbound_value()
+        } else {
+            Symbol {
+                robj: unsafe { new_owned(make_symbol(val)) },
+            }
+        }
+    }
+
+    // Internal conversion for constant symbols.
+    fn from_sexp(sexp: SEXP) -> Symbol {
+        unsafe {
+            assert!(TYPEOF(sexp) == SYMSXP as i32);
+        }
+        Symbol {
+            robj: unsafe { new_sys(sexp) },
+        }
+    }
+
+    /// Get the string from a symbol object.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     assert_eq!(sym!(xyz).as_symbol().unwrap().as_str(), "xyz");
+    /// }
+    /// ```
+    pub fn as_str(&self) -> &str {
+        unsafe {
+            let sexp = self.robj.get();
+            let printname = PRINTNAME(sexp);
+            assert!(TYPEOF(printname) as u32 == CHARSXP);
+            to_str(R_CHAR(printname) as *const u8)
+        }
+    }
+}
+
+impl From<&str> for Symbol {
+    /// Convert a string to a symbol.
+    fn from(name: &str) -> Self {
+        Symbol::from_str(name)
+    }
+}
+
+/// Unbound marker
+pub fn unbound_value() -> Symbol {
+    unsafe { Symbol::from_sexp(R_UnboundValue) }
+}
+
+/// Missing argument marker
+pub fn missing_arg() -> Symbol {
+    unsafe { Symbol::from_sexp(R_MissingArg) }
+}
+
+/// "base"
+pub fn base_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_BaseSymbol) }
+}
+
+/// "{"
+pub fn brace_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_BraceSymbol) }
+}
+
+/// "[["
+pub fn bracket_2_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_Bracket2Symbol) }
+}
+
+/// "["
+pub fn bracket_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_BracketSymbol) }
+}
+
+/// "class"
+pub fn class_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_ClassSymbol) }
+}
+
+/// ".Device"
+pub fn device_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DeviceSymbol) }
+}
+
+/// "dimnames"
+pub fn dimnames_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DimNamesSymbol) }
+}
+
+/// "dim"
+pub fn dim_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DimSymbol) }
+}
+
+/// "$"
+pub fn dollar_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DollarSymbol) }
+}
+
+/// "..."
+pub fn dots_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DotsSymbol) }
+}
+//     pub fn drop_symbol() -> Symbol { unsafe { Symbol::from_sexp(R_DropSymbol) }}"drop"
+
+/// "::"
+pub fn double_colon_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_DoubleColonSymbol) }
+}
+
+/// ".Last.value"
+pub fn lastvalue_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_LastvalueSymbol) }
+}
+/// "levels"
+pub fn levels_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_LevelsSymbol) }
+}
+/// "mode"
+pub fn mode_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_ModeSymbol) }
+}
+/// "na.rm"
+pub fn na_rm_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_NaRmSymbol) }
+}
+/// "name"
+pub fn name_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_NameSymbol) }
+}
+/// "names"
+pub fn names_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_NamesSymbol) }
+}
+/// _NAMESPACE__."
+pub fn namespace_env_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_NamespaceEnvSymbol) }
+}
+/// "package"
+pub fn package_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_PackageSymbol) }
+}
+/// "previous"
+pub fn previous_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_PreviousSymbol) }
+}
+/// "quote"
+pub fn quote_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_QuoteSymbol) }
+}
+/// "row.names"
+pub fn row_names_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_RowNamesSymbol) }
+}
+/// ".Random.seed"
+pub fn seeds_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_SeedsSymbol) }
+}
+/// "sort.list"
+pub fn sort_list_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_SortListSymbol) }
+}
+/// "source"
+pub fn source_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_SourceSymbol) }
+}
+/// "spec"
+pub fn spec_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_SpecSymbol) }
+}
+/// "tsp"
+pub fn tsp_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_TspSymbol) }
+}
+/// ":::"
+pub fn triple_colon_symbol() -> Symbol {
+    unsafe { Symbol::from_sexp(R_TripleColonSymbol) }
+}
+/// ".defined"
+pub fn dot_defined() -> Symbol {
+    unsafe { Symbol::from_sexp(R_dot_defined) }
+}
+/// ".Method"
+pub fn dot_method() -> Symbol {
+    unsafe { Symbol::from_sexp(R_dot_Method) }
+}
+/// "packageName"
+pub fn dot_package_name() -> Symbol {
+    unsafe { Symbol::from_sexp(R_dot_packageName) }
+}
+
+/// ".target"
+pub fn dot_target() -> Symbol {
+    unsafe { Symbol::from_sexp(R_dot_target) }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_constant_symbols() {
+        test! {
+            assert!(unbound_value().is_symbol());
+            assert!(missing_arg().is_symbol());
+            assert!(base_symbol().is_symbol());
+            assert!(brace_symbol().is_symbol());
+            assert!(bracket_2_symbol().is_symbol());
+            assert!(bracket_symbol().is_symbol());
+            assert!(class_symbol().is_symbol());
+            assert!(device_symbol().is_symbol());
+            assert!(dimnames_symbol().is_symbol());
+            assert!(dim_symbol().is_symbol());
+            assert!(dollar_symbol().is_symbol());
+            assert!(dots_symbol().is_symbol());
+            assert!(lastvalue_symbol().is_symbol());
+            assert!(levels_symbol().is_symbol());
+            assert!(mode_symbol().is_symbol());
+            assert!(na_rm_symbol().is_symbol());
+            assert!(name_symbol().is_symbol());
+            assert!(names_symbol().is_symbol());
+            assert!(namespace_env_symbol().is_symbol());
+            assert!(package_symbol().is_symbol());
+            assert!(previous_symbol().is_symbol());
+            assert!(quote_symbol().is_symbol());
+            assert!(row_names_symbol().is_symbol());
+            assert!(seeds_symbol().is_symbol());
+            assert!(sort_list_symbol().is_symbol());
+            assert!(source_symbol().is_symbol());
+            assert!(spec_symbol().is_symbol());
+            assert!(tsp_symbol().is_symbol());
+            assert!(triple_colon_symbol().is_symbol());
+            assert!(dot_defined().is_symbol());
+            assert!(dot_method().is_symbol());
+            assert!(dot_package_name().is_symbol());
+            assert!(dot_target().is_symbol());
+        }
+    }
+}
diff --git a/extendr-macros/src/lib.rs b/extendr-macros/src/lib.rs
index 62b2b8b6c..a68364593 100644
--- a/extendr-macros/src/lib.rs
+++ b/extendr-macros/src/lib.rs
@@ -496,7 +496,7 @@ fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
         impl<'a> extendr_api::FromRobj<'a> for &#self_ty {
             fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
                 if robj.check_external_ptr(#self_ty_name) {
-                    Ok(unsafe { std::mem::transmute(robj.externalPtrAddr::<#self_ty>()) })
+                    Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<#self_ty>()) })
                 } else {
                     Err(concat!("expected ", #self_ty_name))
                 }
@@ -507,7 +507,7 @@ fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
         impl<'a> extendr_api::FromRobj<'a> for &mut #self_ty {
             fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
                 if robj.check_external_ptr(#self_ty_name) {
-                    Ok(unsafe { std::mem::transmute(robj.externalPtrAddr::<#self_ty>()) })
+                    Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<#self_ty>()) })
                 } else {
                     Err(concat!("expected ", #self_ty_name))
                 }
@@ -519,9 +519,9 @@ fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
             fn from(value: #self_ty) -> Self {
                 unsafe {
                     let ptr = Box::into_raw(Box::new(value));
-                    let res = Robj::makeExternalPtr(ptr, Robj::from(#self_ty_name), Robj::from(()));
+                    let res = Robj::make_external_ptr(ptr, Robj::from(#self_ty_name), Robj::from(()));
                     res.set_attrib(class_symbol(), #self_ty_name).unwrap();
-                    res.registerCFinalizer(Some(#finalizer_name));
+                    res.register_c_finalizer(Some(#finalizer_name));
                     res
                 }
             }
@@ -532,9 +532,9 @@ fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
             fn from(value: &'a #self_ty) -> Self {
                 unsafe {
                     let ptr = Box::into_raw(Box::new(value));
-                    let res = Robj::makeExternalPtr(ptr, Robj::from(#self_ty_name), Robj::from(()));
+                    let res = Robj::make_external_ptr(ptr, Robj::from(#self_ty_name), Robj::from(()));
                     res.set_attrib(class_symbol(), #self_ty_name).unwrap();
-                    res.registerCFinalizer(Some(#finalizer_name));
+                    res.register_c_finalizer(Some(#finalizer_name));
                     res
                 }
             }
@@ -546,7 +546,7 @@ fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
                 let robj = extendr_api::new_owned(sexp);
                 if robj.check_external_ptr(#self_ty_name) {
                     //eprintln!("finalize {}", #self_ty_name);
-                    let ptr = robj.externalPtrAddr::<#self_ty>();
+                    let ptr = robj.external_ptr_addr::<#self_ty>();
                     Box::from_raw(ptr);
                 }
             }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 134f8b967..32131cec2 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -118,6 +118,7 @@ jobs:
         run: |
           Del alias:R
           echo "LD_LIBRARY_PATH=$(R -s -e 'cat(normalizePath(R.home()))')/lib" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
       # Check code formatting. As this doesn't depend on the platform, do this only on one platform.
       - name: Check code formatting
         if: matrix.config.check_fmt
@@ -167,6 +168,13 @@ jobs:
           key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
           restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-
 
+      - name: Install system dependencies for rextendr
+        if: startsWith(runner.os, 'linux')
+        run: |
+          foreach($apt_cmd in $(Rscript -e "writeLines(remotes::system_requirements('ubuntu', '20.04', path = 'tests/rextendr'))")) {
+            Invoke-Expression "sudo $apt_cmd"
+          }
+
       # Currently rextendr requires R > 4, so if R version is older (e.g. 3.6.3), rextendr tests are omitted
       # Regex is used to inject absolute path into Cargo.toml
       - name: Install dependencies & configure R for integration testing
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index d73ee71f1..3dc7195b5 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -18,14 +18,14 @@ fn test_debug() {
         // Vectors
         assert_eq!(format!("{:?}", r!([1, 2, 3])), "r!([1, 2, 3])");
         assert_eq!(format!("{:?}", r!([1., 2., 3.])), "r!([1.0, 2.0, 3.0])");
-        assert_eq!(format!("{:?}", r!(Raw(&[1, 2, 3]))), "r!(Raw([1, 2, 3]))");
+        assert_eq!(format!("{:?}", r!(Raw::from_bytes(&[1, 2, 3]))), "r!(Raw::from_bytes([1, 2, 3]))");
 
         // Wrappers
-        assert_eq!(format!("{:?}", r!(Symbol("x"))), "sym!(x)");
-        assert_eq!(format!("{:?}", r!(Character("x"))), "r!(Character(\"x\"))");
+        assert_eq!(format!("{:?}", r!(Symbol::from_str("x"))), "sym!(x)");
+        assert_eq!(format!("{:?}", r!(Character::from_str("x"))), "r!(Character::from_str(\"x\"))");
         assert_eq!(
-            format!("{:?}", r!(Lang(&[r!(Symbol("x"))]))),
-            "r!(Lang([sym!(x)]))"
+            format!("{:?}", r!(Language::from_values(&[r!(Symbol::from_str("x"))]))),
+            "r!(Language::from_values([sym!(x)]))"
         );
 
         // Logical
@@ -127,6 +127,100 @@ fn test_from_robj() {
         assert!(<Option<String>>::from_robj(&Robj::from(["1", "2"])).is_err());
     }
 }
+
+#[test]
+fn test_try_from_robj() {
+    test! {
+        assert_eq!(<bool>::try_from(Robj::from(true)), Ok(true));
+        assert_eq!(<u8>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<u16>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<u32>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<u64>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<i8>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<i16>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<i32>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<i64>::try_from(Robj::from(1)), Ok(1));
+        assert_eq!(<f32>::try_from(Robj::from(1)), Ok(1.));
+        assert_eq!(<f64>::try_from(Robj::from(1)), Ok(1.));
+
+        assert_eq!(<Vec::<i32>>::try_from(Robj::from(1)), Ok(vec![1]));
+        assert_eq!(<Vec::<f64>>::try_from(Robj::from(1.)), Ok(vec![1.]));
+
+        let hello = Robj::from("hello");
+        assert_eq!(<&str>::try_from(hello), Ok("hello"));
+
+        // conversion from a vector to a scalar value
+        let robj = Robj::from(vec![].as_slice() as &[i32]);
+        assert_eq!(
+            <i32>::try_from(robj.clone()),
+            Err(Error::ExpectedNonZeroLength(robj))
+        );
+        assert_eq!(
+            <i32>::try_from(Robj::from(vec![1].as_slice() as &[i32])),
+            Ok(1)
+        );
+        let robj = Robj::from(vec![1, 2].as_slice() as &[i32]);
+        assert_eq!(
+            <i32>::try_from(robj.clone()),
+            Err(Error::ExpectedScalar(robj))
+        );
+
+        use std::collections::HashMap;
+        let list = 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>>::try_from(list.clone()).unwrap();
+        let hmap_borrowed = <HashMap<&str, Robj>>::try_from(list.clone()).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 = eval_string("NA_integer_").unwrap();
+        assert!(<i32>::try_from(na_integer.clone()).is_err());
+        assert_eq!(<Option<i32>>::try_from(na_integer.clone()), Ok(None));
+        assert_eq!(<Option<i32>>::try_from(Robj::from(1)), Ok(Some(1)));
+        assert!(<Option<i32>>::try_from(Robj::from([1, 2])).is_err());
+
+        let na_bool = eval_string("TRUE == NA").unwrap();
+        assert!(<bool>::try_from(na_bool.clone()).is_err());
+        assert_eq!(<Option<bool>>::try_from(na_bool.clone()), Ok(None));
+        assert_eq!(<Option<bool>>::try_from(Robj::from(true)), Ok(Some(true)));
+        assert!(<Option<bool>>::try_from(Robj::from([true, false])).is_err());
+
+        let na_real = eval_string("NA_real_").unwrap();
+        assert!(<f64>::try_from(na_real.clone()).is_err());
+        assert_eq!(<Option<f64>>::try_from(na_real.clone()), Ok(None));
+        assert_eq!(<Option<f64>>::try_from(Robj::from(1.)), Ok(Some(1.)));
+        assert!(<Option<f64>>::try_from(Robj::from([1., 2.])).is_err());
+
+        let na_string = eval_string("NA_character_").unwrap();
+        assert!(<&str>::try_from(na_string.clone()).is_err());
+        assert_eq!(<Option<&str>>::try_from(na_string.clone()), Ok(None));
+        assert_eq!(<Option<&str>>::try_from(Robj::from("1")), Ok(Some("1")));
+        assert!(<Option<&str>>::try_from(Robj::from(["1", "2"])).is_err());
+
+        let na_string = eval_string("NA_character_").unwrap();
+        assert!(<String>::try_from(na_string.clone()).is_err());
+        assert_eq!(<Option<String>>::try_from(na_string.clone()), Ok(None));
+        assert_eq!(
+            <Option<String>>::try_from(Robj::from("1")),
+            Ok(Some("1".to_string()))
+        );
+        assert!(<Option<String>>::try_from(Robj::from(["1", "2"])).is_err());
+    }
+}
 #[test]
 fn test_to_robj() {
     test! {
@@ -170,9 +264,9 @@ fn test_to_robj() {
 fn parse_test() {
     test! {
     let p = parse("print(1L);print(1L);")?;
-    let q = r!(Expr(&[
-        r!(Lang(&[r!(Symbol("print")), r!(1)])),
-        r!(Lang(&[r!(Symbol("print")), r!(1)]))
+    let q = r!(Expression::from_values(&[
+        r!(Language::from_values(&[r!(Symbol::from_str("print")), r!(1)])),
+        r!(Language::from_values(&[r!(Symbol::from_str("print")), r!(1)]))
     ]));
     assert_eq!(p, q);
 
@@ -238,7 +332,7 @@ fn input_iterator_test() {
         assert_eq!(iter.collect::<Vec<_>>(), src);
 
         let src = &[Robj::from(1), Robj::from(2), Robj::from(3)];
-        let robj = Robj::from(List(src));
+        let robj = Robj::from(List::from_values(src));
         let iter = <ListIter>::from_robj(&robj).unwrap();
         assert_eq!(iter.collect::<Vec<_>>(), src);