Repository: extendr/extendr
Pull request #666 (2023-11-05 10:53:05)

Added unsafe `SendSEXP` to `ownership`-module
Currently, the `Ownership` module converts all `SEXP` to `usize` (?), such that they become `Send`, so that the ownership module can be used from multiple threads.

- Wrap `SEXP` in an explicit `Send` type. Accessing the pointer would require safety comments, as it can only be accessed through `inner` and `set_inner`. 
- No pointer conversion to `usize` and back.
- Prototype for an eventual support for an unsafe `SendRobj`. 

## Background

This came up when looking at why tests started to fail sporadically. This isn't meant to fix anything in particular,
but it adds an explicit unsafe layer that is involved with what the `ownership`-module currently does. If we get rid of multithreaded support, we'd still provide users with something similar to this, or they would write something themselves, see #659 for discussion. 

sorhawell: I'm not familiar with the ownership.rs module.

As I read this PR, it might be compiled down to the very same binary.
I think the aim of this PR makes sense to not hide pointers in plain usize, whenever possible.
CGMossa: > I'm not familiar with the ownership.rs module.
> 
> As I read this PR, it might be compiled down to the very same binary before and after. I think the aim of this PR makes sense to not hide pointers in plain usize, whenever possible.

Thanks for your comment. It is indeed my intention not to convert pointers to `usize` and back again. I can't find a reason why this is definitively bad, but it is unnecessary and the intent is unclear.
CGMossa: I've rebased and removed commented code.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1d8db1f1..ea77e9e0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,14 +2,19 @@
 
 ## Unreleased
 
-## Added
+### Added
 
 - `RArray::data_mut` provides a mutable slice to the underlying array data [[#657]](https://github.com/extendr/extendr/pull/657)
 
-## Changed
+### Changed
 
 - Potentially breaking: `RArray::from_parts` no longer requires a pointer to the underlying data vector [[#657]](https://github.com/extendr/extendr/pull/657)
 
+### Fixed
+
+- returning `&Self` or `&mut Self` from a method in an `#[extendr]`-impl would
+result in unintended cloning  [[[#614](https://github.com/extendr/extendr/issues/614)]]
+
 ## 0.6.0
 
 ### Added
diff --git a/extendr-api/Cargo.toml b/extendr-api/Cargo.toml
index 382a267d0..e3733d1be 100644
--- a/extendr-api/Cargo.toml
+++ b/extendr-api/Cargo.toml
@@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"], optional = true }
 
 [dev-dependencies]
 extendr-engine = { path = "../extendr-engine" }
-rstest = "0.18.1"
+rstest = "0.19.0"
 
 [features]
 
diff --git a/extendr-api/src/ownership.rs b/extendr-api/src/ownership.rs
index 9115c161d..f8eeccdae 100644
--- a/extendr-api/src/ownership.rs
+++ b/extendr-api/src/ownership.rs
@@ -17,6 +17,38 @@ use libR_sys::{
     Rf_unprotect, LENGTH, SET_VECTOR_ELT, SEXP, VECSXP, VECTOR_ELT,
 };
 
+mod send_sexp {
+    //! Provide a wrapper around R's pointer type `SEXP` that is `Send`.
+    //!
+    //! This can lead to soundness issues, therefore accessing the `SEXP` has
+    //! to happen through the unsafe method [`SendSEXP::inner`].
+    //!
+    use libR_sys::SEXP;
+
+    /// A wrapper around R's pointer type `SEXP` that is `Send`.
+    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+    pub struct SendSEXP(SEXP);
+
+    impl From<SEXP> for SendSEXP {
+        fn from(value: SEXP) -> Self {
+            Self(value)
+        }
+    }
+
+    // Allows SendSEXP to be sent between threads even though unsafe
+    // Requires that the SEXP is not accessed concurrently.
+    unsafe impl Send for SendSEXP {}
+
+    impl SendSEXP {
+        /// Get the inner `SEXP`
+        pub unsafe fn inner(&self) -> SEXP {
+            self.0
+        }
+    }
+}
+
+use self::send_sexp::SendSEXP;
+
 static OWNERSHIP: Lazy<Mutex<Ownership>> = Lazy::new(|| Mutex::new(Ownership::new()));
 
 pub(crate) unsafe fn protect(sexp: SEXP) {
@@ -32,15 +64,20 @@ pub(crate) unsafe fn unprotect(sexp: SEXP) {
 pub const INITIAL_PRESERVATION_SIZE: usize = 100000;
 pub const EXTRA_PRESERVATION_SIZE: usize = 100000;
 
+// `Object` is a manual reference counting mechanism that is used for each SEXP.
+// `refcount` is the number of times the SEXP is accessed.
+// `index` is the index of the SEXP in the preservation vector.
+#[derive(Debug)]
 struct Object {
     refcount: usize,
     index: usize,
 }
 
 // A reference counted object with an index in the preservation vector.
+#[derive(Debug)]
 struct Ownership {
     // A growable vector containing all owned objects.
-    preservation: usize,
+    preservation: SendSEXP,
 
     // An incrementing count of objects through the vector.
     cur_index: usize,
@@ -49,7 +86,7 @@ struct Ownership {
     max_index: usize,
 
     // A hash map from SEXP address to object.
-    objects: HashMap<usize, Object>,
+    objects: HashMap<SendSEXP, Object>,
 }
 
 impl Ownership {
@@ -58,7 +95,7 @@ impl Ownership {
             let preservation = Rf_allocVector(VECSXP, INITIAL_PRESERVATION_SIZE as R_xlen_t);
             R_PreserveObject(preservation);
             Ownership {
-                preservation: preservation as usize,
+                preservation: preservation.into(),
                 cur_index: 0,
                 max_index: INITIAL_PRESERVATION_SIZE,
                 objects: HashMap::with_capacity(INITIAL_PRESERVATION_SIZE),
@@ -66,14 +103,54 @@ impl Ownership {
         }
     }
 
+    // Garbage collect the tracking structures.
+    unsafe fn garbage_collect(&mut self) {
+        let new_size = self.cur_index * 2 + EXTRA_PRESERVATION_SIZE;
+        let new_sexp = Rf_allocVector(VECSXP, new_size as R_xlen_t);
+        R_PreserveObject(new_sexp);
+        let old_sexp = self.preservation.inner();
+
+        let mut new_objects = HashMap::with_capacity(new_size);
+
+        // copy non-null elements to new vector and hashmap.
+        let mut j = 0;
+        for (addr, object) in self.objects.iter() {
+            if object.refcount != 0 {
+                SET_VECTOR_ELT(new_sexp, j as R_xlen_t, addr.inner());
+                new_objects.insert(
+                    addr.clone(),
+                    Object {
+                        refcount: object.refcount,
+                        index: j,
+                    },
+                );
+                j += 1;
+            }
+        }
+
+        R_ReleaseObject(old_sexp);
+        self.preservation = (new_sexp).into();
+        self.cur_index = j;
+        self.max_index = new_size;
+        self.objects = new_objects;
+    }
+
     unsafe fn protect(&mut self, sexp: SEXP) {
+        // This protects the SEXP. Is this necessary?
+        // Because the Ownership object already protects an SEXP in the `preservation` field.
+        // The new `sexp` is inserted into the preservation list via `SET_VECTOR_ELT` below.
+        // If list is protected then so are all of its elements.
+        //
+        // > Protecting an R object automatically protects all the R objects
+        // > pointed to in the corresponding SEXPREC, for example all elements
+        // > of a protected list are automatically protected." 5.9.1
         Rf_protect(sexp);
 
         if self.cur_index == self.max_index {
             self.garbage_collect();
         }
 
-        let sexp_usize = sexp as usize;
+        let send_sexp = sexp.into();
         let Ownership {
             ref mut preservation,
             ref mut cur_index,
@@ -81,8 +158,8 @@ impl Ownership {
             ref mut objects,
         } = *self;
 
-        let mut entry = objects.entry(sexp_usize);
-        let preservation_sexp = *preservation as SEXP;
+        let mut entry = objects.entry(send_sexp);
+        let preservation_sexp = preservation.inner();
         match entry {
             Entry::Occupied(ref mut occupied) => {
                 if occupied.get().refcount == 0 {
@@ -105,15 +182,15 @@ impl Ownership {
     }
 
     pub unsafe fn unprotect(&mut self, sexp: SEXP) {
-        let sexp_usize = sexp as usize;
+        let send_sexp = sexp.into();
         let Ownership {
             preservation,
             cur_index: _,
             max_index: _,
             ref mut objects,
-        } = *self;
+        } = self;
 
-        let mut entry = objects.entry(sexp_usize);
+        let mut entry = objects.entry(send_sexp);
         match entry {
             Entry::Occupied(ref mut occupied) => {
                 let object = occupied.get_mut();
@@ -125,7 +202,7 @@ impl Ownership {
                         // Clear the preservation vector, but keep the hash table entry.
                         // It is hard to clear the hash table entry here because we don't
                         // have a ref to objects anymore and it is faster to clear them up en-masse.
-                        let preservation_sexp = preservation as SEXP;
+                        let preservation_sexp = preservation.inner();
                         SET_VECTOR_ELT(preservation_sexp, object.index as R_xlen_t, R_NilValue);
                     }
                 }
@@ -145,52 +222,17 @@ impl Ownership {
             ref mut objects,
         } = *self;
 
-        let sexp_usize = sexp as usize;
-        let mut entry = objects.entry(sexp_usize);
+        let mut entry = objects.entry(sexp.into());
         match entry {
             Entry::Occupied(ref mut occupied) => occupied.get().refcount,
             Entry::Vacant(_) => 0,
         }
     }
 
-    // Garbage collect the tracking structures.
-    unsafe fn garbage_collect(&mut self) {
-        // println!("garbage_collect {} {}", self.cur_index, self.max_index);
-        let new_size = self.cur_index * 2 + EXTRA_PRESERVATION_SIZE;
-        let new_sexp = Rf_allocVector(VECSXP, new_size as R_xlen_t);
-        R_PreserveObject(new_sexp);
-        let old_sexp = self.preservation as SEXP;
-
-        let mut new_objects = HashMap::with_capacity(new_size);
-
-        // copy non-null elements to new vector and hashmap.
-        let mut j = 0;
-        for (addr, object) in self.objects.iter() {
-            if object.refcount != 0 {
-                SET_VECTOR_ELT(new_sexp, j as R_xlen_t, *addr as SEXP);
-                new_objects.insert(
-                    *addr,
-                    Object {
-                        refcount: object.refcount,
-                        index: j,
-                    },
-                );
-                j += 1;
-            }
-        }
-        // println!("j={}", j);
-
-        R_ReleaseObject(old_sexp);
-        self.preservation = new_sexp as usize;
-        self.cur_index = j;
-        self.max_index = new_size;
-        self.objects = new_objects;
-    }
-
     // Check the consistency of the model.
     #[allow(dead_code)]
     unsafe fn check_objects(&mut self) {
-        let preservation_sexp = self.preservation as SEXP;
+        let preservation_sexp = self.preservation.inner();
         assert_eq!(self.max_index, LENGTH(preservation_sexp) as usize);
 
         // println!("\ncheck");
@@ -204,7 +246,7 @@ impl Ownership {
             // );
             if object.refcount != 0 {
                 // A non-zero refcount implies the object is in the vector.
-                assert_eq!(elt, *addr as SEXP);
+                assert_eq!(elt, addr.inner());
             } else {
                 // A zero refcount implies the object is NULL in the vector.
                 assert_eq!(elt, R_NilValue);
@@ -228,7 +270,7 @@ impl Ownership {
 mod test {
     use super::*;
     use crate::*;
-    use libR_sys::{Rf_ScalarInteger, Rf_protect, Rf_unprotect};
+    use libR_sys::{Rf_ScalarInteger, Rf_protect};
 
     #[test]
     fn basic_test() {
diff --git a/extendr-api/src/wrapper/dataframe.rs b/extendr-api/src/wrapper/dataframe.rs
index 482417632..35a314bd5 100644
--- a/extendr-api/src/wrapper/dataframe.rs
+++ b/extendr-api/src/wrapper/dataframe.rs
@@ -4,24 +4,47 @@ pub trait IntoDataFrameRow<T> {
     fn into_dataframe(self) -> Result<Dataframe<T>>;
 }
 
+/// A representation of a typed `data.frame`
+///
+/// A `data.frame` can be created from Rust by using the [`IntoDataFrameRow`] trait
+/// which can be derived for a single `struct` that represents a single row.
+/// The type of the row is captured by the marker `T`.
+///
+/// Note that at present, you can create a `Dataframe<T>` but you cannot extract
+/// `T` from the object. `<T>` is purely a marker that indicates the struct that
+/// was used to create its rows.
+///
+/// As a result, using `Dataframe<T>` as a function argument _will not_ perform
+/// any type checking on the type.
 #[derive(PartialEq, Clone)]
 pub struct Dataframe<T> {
     pub(crate) robj: Robj,
-    marker: std::marker::PhantomData<T>,
+    _marker: std::marker::PhantomData<T>,
+}
+
+impl<T> From<Dataframe<T>> for Robj {
+    fn from(value: Dataframe<T>) -> Self {
+        value.robj
+    }
+}
+
+impl<T> FromRobj<'_> for Dataframe<T> {
+    fn from_robj(robj: &Robj) -> std::result::Result<Self, &'static str> {
+        robj.try_into().or(Err("expected a `data.frame`"))
+    }
 }
 
 impl<T> std::convert::TryFrom<&Robj> for Dataframe<T> {
     type Error = Error;
     fn try_from(robj: &Robj) -> Result<Self> {
         // TODO: check type using derived trait.
-        if robj.is_list() && robj.inherits("data.frame") {
-            Ok(Dataframe {
-                robj: robj.clone(),
-                marker: std::marker::PhantomData,
-            })
-        } else {
-            Err(Error::ExpectedDataframe(robj.clone()))
+        if !(robj.is_list() && robj.inherits("data.frame")) {
+            return Err(Error::ExpectedDataframe(robj.clone()));
         }
+        Ok(Dataframe {
+            robj: robj.clone(),
+            _marker: std::marker::PhantomData,
+        })
     }
 }
 
@@ -62,9 +85,3 @@ where
         )
     }
 }
-
-impl<T> IntoRobj for Dataframe<T> {
-    fn into_robj(self) -> Robj {
-        self.robj
-    }
-}
diff --git a/extendr-macros/Cargo.toml b/extendr-macros/Cargo.toml
index 939f13f9e..c1a77c4ac 100644
--- a/extendr-macros/Cargo.toml
+++ b/extendr-macros/Cargo.toml
@@ -19,3 +19,4 @@ proc-macro2 = { version = "1.0" }
 extendr-api = { path = "../extendr-api" }
 extendr-engine = { path = "../extendr-engine" }
 libR-sys = { workspace = true }
+trybuild = "1.0"
diff --git a/extendr-macros/src/extendr_function.rs b/extendr-macros/src/extendr_function.rs
index 2c841ac7c..c220781ee 100644
--- a/extendr-macros/src/extendr_function.rs
+++ b/extendr-macros/src/extendr_function.rs
@@ -6,7 +6,12 @@ use syn::{meta::ParseNestedMeta, ItemFn, Lit, LitBool};
 /// Generate bindings for a single function.
 pub fn extendr_function(mut func: ItemFn, opts: &wrappers::ExtendrOptions) -> TokenStream {
     let mut wrappers: Vec<ItemFn> = Vec::new();
-    wrappers::make_function_wrappers(opts, &mut wrappers, "", &func.attrs, &mut func.sig, None);
+
+    let res =
+        wrappers::make_function_wrappers(opts, &mut wrappers, "", &func.attrs, &mut func.sig, None);
+    if let Err(e) = res {
+        return e.into_compile_error().into();
+    };
 
     TokenStream::from(quote! {
         #func
@@ -25,45 +30,46 @@ impl wrappers::ExtendrOptions {
     /// - `use_rng = bool` ensures the RNG-state is pulled and pushed
     ///
     pub fn parse(&mut self, meta: ParseNestedMeta) -> syn::parse::Result<()> {
-        fn help_message() -> ! {
-            panic!("expected #[extendr(use_try_from = bool, r_name = \"name\", mod_name = \"r_mod_name\", use_rng = bool)]");
-        }
-
-        let value = match meta.value() {
-            Ok(value) => value,
-            Err(_) => help_message(),
-        };
+        let value = meta.value()?;
+        let path = meta
+            .path
+            .get_ident()
+            .ok_or(meta.error("Unexpected syntax"))?;
 
-        if meta.path.is_ident("use_try_from") {
-            if let Ok(LitBool { value, .. }) = value.parse() {
-                self.use_try_from = value;
-                Ok(())
-            } else {
-                help_message();
+        match path.to_string().as_str() {
+            "use_try_from" => {
+                if let Ok(LitBool { value, .. }) = value.parse() {
+                    self.use_try_from = value;
+                    Ok(())
+                } else {
+                    Err(value.error("`use_try_from` must be `true` or `false`"))
+                }
             }
-        } else if meta.path.is_ident("r_name") {
-            if let Ok(Lit::Str(litstr)) = value.parse() {
-                self.r_name = Some(litstr.value());
-                Ok(())
-            } else {
-                help_message();
+            "r_name" => {
+                if let Ok(Lit::Str(litstr)) = value.parse() {
+                    self.r_name = Some(litstr.value());
+                    Ok(())
+                } else {
+                    Err(value.error("`r_name` must be a string literal"))
+                }
             }
-        } else if meta.path.is_ident("mod_name") {
-            if let Ok(Lit::Str(litstr)) = value.parse() {
-                self.mod_name = Some(litstr.value());
-                Ok(())
-            } else {
-                help_message();
+            "mod_name" => {
+                if let Ok(Lit::Str(litstr)) = value.parse() {
+                    self.mod_name = Some(litstr.value());
+                    Ok(())
+                } else {
+                    Err(value.error("`mod_name` must be a string literal"))
+                }
             }
-        } else if meta.path.is_ident("use_rng") {
-            if let Ok(LitBool { value, .. }) = value.parse() {
-                self.use_rng = value;
-                Ok(())
-            } else {
-                help_message();
+            "use_rng" => {
+                if let Ok(LitBool { value, .. }) = value.parse() {
+                    self.use_rng = value;
+                    Ok(())
+                } else {
+                    Err(value.error("`use_rng` must be `true` or `false`"))
+                }
             }
-        } else {
-            help_message();
+            _ => Err(syn::Error::new_spanned(meta.path, "Unexpected key")),
         }
     }
 }
diff --git a/extendr-macros/src/extendr_impl.rs b/extendr-macros/src/extendr_impl.rs
index 16f4a2f7c..25b060062 100644
--- a/extendr-macros/src/extendr_impl.rs
+++ b/extendr-macros/src/extendr_impl.rs
@@ -5,53 +5,134 @@ use syn::{ItemFn, ItemImpl};
 
 use crate::wrappers;
 
-/// Handle trait implementations.
+/// Make inherent implementations available to R
+///
+/// The extendr_impl function is used to make inherent implementations
+/// avaialble to R as an environment. By adding the [`extendr`] attribute
+/// macro to an `impl` block (supported with `enum`s and `struct`s), the
+/// methods in the impl block are made available as functions in an
+/// environment.
+///
+/// On the R side, an environment with the same name of the inherent
+/// implementation is created. The environment has functions within it
+/// that correspond to each method in the impl block. Note that in order
+/// for an impl block to be compatible with extendr (and thus R), its return
+/// type must be able to be returned to R. For example, any struct that might
+/// be returned must _also_ have an `#[extendr]` annotated impl block.
 ///
 /// Example:
 /// ```ignore
 /// use extendr_api::prelude::*;
-/// #[derive(Debug)]
+///
+/// // a struct that will be used internal the People struct
+/// #[derive(Clone, Debug, IntoDataFrameRow)]
 /// struct Person {
-///     pub name: String,
+///     name: String,
+///     age: i32,
 /// }
+///
+/// // This will collect people in the struct
+/// #[extendr]
+/// #[derive(Clone, Debug)]
+/// struct People(Vec<Person>);
+///
 /// #[extendr]
-/// impl Person {
+/// /// @export
+/// impl People {
+///
+///     // instantiate a new struct with an empty vector
 ///     fn new() -> Self {
-///         Self { name: "".to_string() }
+///         let vec: Vec<Person> = Vec::new();
+///         Self(vec)
 ///     }
-///     fn set_name(&mut self, name: &str) {
-///         self.name = name.to_string();
+///
+///     // add a person to the internal vector
+///     fn add_person(&mut self, name: &str, age: i32) -> &mut Self {
+///         let person = Person {
+///             name: String::from(name),
+///             age: age,
+///         };
+///
+///         self.0.push(person);
+///
+///         // return self
+///         self
 ///     }
-///     fn name(&self) -> &str {
-///         self.name.as_str()
+///     
+///     // Convert the struct into a data.frame
+///     fn into_df(&self) -> Robj {
+///         let df = self.0.clone().into_dataframe();
+///
+///         match df {
+///             Ok(df) => df.as_robj().clone(),
+///             Err(_) => data_frame!(),
+///         }
 ///     }
+///
+///     // add another `People` struct to self
+///     fn add_people(&mut self, others: &People) -> &mut Self {
+///         self.0.extend(others.0.clone().into_iter());
+///         self
+///     }
+///
+///     // create a function to print the self which can be called
+///     // from an R print method
+///     fn print_self(&self) -> String {
+///         format!("{:?}", self.0)
+///     }
+///
 /// }
-/// #[extendr]
-/// fn aux_func() {
-/// }
-/// // Macro to generate exports
+///
+/// // Macro to generate exports.
+/// // This ensures exported functions are registered with R.
+/// // See corresponding C code in `entrypoint.c`.
 /// extendr_module! {
-///     mod classes;
-///     impl Person;
-///     fn aux_func;
+///     mod testself;
+///     impl People;
+/// }
+/// ```
+///     
+/// **Known limitation**: if you return `&Self` or `&mut Self` or a reference
+/// to the same type, the resultant R object will _always_ be the original
+/// self. For example the below method **will not** return `other`.
+///
+/// ```ignore
+///  // This is not possible today
+/// #[extendr]
+/// impl People {
+///   fn return_other(&self, other: &'static T) -> &Self {
+///     other
+///   }
 /// }
 /// ```
-pub fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
+pub fn extendr_impl(mut item_impl: ItemImpl) -> syn::Result<TokenStream> {
     // Only `impl name { }` allowed
     if item_impl.defaultness.is_some() {
-        return quote! { compile_error!("default not allowed in #[extendr] impl"); }.into();
+        return Err(syn::Error::new_spanned(
+            item_impl,
+            "default not allowed in #[extendr] impl",
+        ));
     }
 
     if item_impl.unsafety.is_some() {
-        return quote! { compile_error!("unsafe not allowed in #[extendr] impl"); }.into();
+        return Err(syn::Error::new_spanned(
+            item_impl,
+            "unsafe not allowed in #[extendr] impl",
+        ));
     }
 
     if item_impl.generics.const_params().count() != 0 {
-        return quote! { compile_error!("const params not allowed in #[extendr] impl"); }.into();
+        return Err(syn::Error::new_spanned(
+            item_impl,
+            "const params not allowed in #[extendr] impl",
+        ));
     }
 
     if item_impl.generics.type_params().count() != 0 {
-        return quote! { compile_error!("type params not allowed in #[extendr] impl"); }.into();
+        return Err(syn::Error::new_spanned(
+            item_impl,
+            "type params not allowed in #[extendr] impl",
+        ));
     }
 
     // if item_impl.generics.lifetimes().count() != 0 {
@@ -59,7 +140,10 @@ pub fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
     // }
 
     if item_impl.generics.where_clause.is_some() {
-        return quote! { compile_error!("where clause not allowed in #[extendr] impl"); }.into();
+        return Err(syn::Error::new_spanned(
+            item_impl,
+            "where clause not allowed in #[extendr] impl",
+        ));
     }
 
     let opts = wrappers::ExtendrOptions::default();
@@ -97,7 +181,7 @@ pub fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
                 &method.attrs,
                 &mut method.sig,
                 Some(self_ty),
-            );
+            )?;
         }
     }
 
@@ -187,7 +271,7 @@ pub fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
     });
 
     //eprintln!("{}", expanded);
-    expanded
+    Ok(expanded)
 }
 
 // This structure contains parameters parsed from the #[extendr_module] definition.
diff --git a/extendr-macros/src/lib.rs b/extendr-macros/src/lib.rs
index 6f3296bd8..4336efea1 100644
--- a/extendr-macros/src/lib.rs
+++ b/extendr-macros/src/lib.rs
@@ -80,7 +80,10 @@ pub fn extendr(attr: TokenStream, item: TokenStream) -> TokenStream {
 
     match parse_macro_input!(item as Item) {
         Item::Fn(func) => extendr_function::extendr_function(func, &opts),
-        Item::Impl(item_impl) => extendr_impl::extendr_impl(item_impl),
+        Item::Impl(item_impl) => match extendr_impl::extendr_impl(item_impl) {
+            Ok(result) => result,
+            Err(e) => e.into_compile_error().into(),
+        },
         other_item => TokenStream::from(quote! {#other_item}),
     }
 }
@@ -203,7 +206,10 @@ pub fn Rraw(item: TokenStream) -> TokenStream {
 /// ```
 #[proc_macro_derive(TryFromRobj)]
 pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
-    list_struct::derive_try_from_robj(item)
+    match list_struct::derive_try_from_robj(item) {
+        Ok(result) => result,
+        Err(e) => e.into_compile_error().into(),
+    }
 }
 
 /// Derives an implementation of `From<Struct> for Robj` and `From<&Struct> for Robj` on this struct.
@@ -241,7 +247,10 @@ pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
 /// and converting it back to Rust will produce a copy of the original struct.
 #[proc_macro_derive(IntoRobj)]
 pub fn derive_into_robj(item: TokenStream) -> TokenStream {
-    list_struct::derive_into_robj(item)
+    match list_struct::derive_into_robj(item) {
+        Ok(result) => result,
+        Err(e) => e.into_compile_error().into(),
+    }
 }
 
 /// Enable the construction of dataframes from arrays of structures.
diff --git a/extendr-macros/src/list_struct.rs b/extendr-macros/src/list_struct.rs
index eb64aade7..4369c1a5f 100644
--- a/extendr-macros/src/list_struct.rs
+++ b/extendr-macros/src/list_struct.rs
@@ -1,19 +1,18 @@
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::quote;
-use syn::{parse_macro_input, Data, DeriveInput};
+use syn::{Data, DeriveInput};
 
 /// Implementation of the TryFromRobj macro. Refer to the documentation there
-pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
+pub fn derive_try_from_robj(item: TokenStream) -> syn::parse::Result<TokenStream> {
     // Parse the tokens into a Struct
-    let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
-    let struct_name = ast.ident;
-    let inside;
-    if let Data::Struct(inner) = ast.data {
-        inside = inner;
+    let ast = syn::parse::<DeriveInput>(item)?;
+    let inside = if let Data::Struct(inner) = ast.data {
+        inner
     } else {
-        panic!("This is a derive macro, only use it on a struct")
+        return Err(syn::Error::new_spanned(ast, "Only struct is supported"));
     };
+    let struct_name = ast.ident;
 
     // Iterate each struct field and capture a conversion from Robj for each field
     let mut tokens = Vec::<TokenStream2>::with_capacity(inside.fields.len());
@@ -27,7 +26,7 @@ pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
     }
 
     // Emit the conversion trait impl
-    TokenStream::from(quote!(
+    Ok(TokenStream::from(quote!(
         impl std::convert::TryFrom<&Robj> for #struct_name {
             type Error = extendr_api::Error;
 
@@ -47,20 +46,19 @@ pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
                 })
             }
         }
-    ))
+    )))
 }
 
 /// Implementation of the IntoRobj macro. Refer to the documentation there
-pub fn derive_into_robj(item: TokenStream) -> TokenStream {
+pub fn derive_into_robj(item: TokenStream) -> syn::parse::Result<TokenStream> {
     // Parse the tokens into a Struct
-    let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
-    let struct_name = ast.ident;
-    let inside;
-    if let Data::Struct(inner) = ast.data {
-        inside = inner;
+    let ast = syn::parse::<DeriveInput>(item)?;
+    let inside = if let Data::Struct(inner) = ast.data {
+        inner
     } else {
-        panic!("This is a derive macro, only use it on a struct")
+        return Err(syn::Error::new_spanned(ast, "Only struct is supported"));
     };
+    let struct_name = ast.ident;
 
     // Iterate each struct field and capture a token that creates a KeyValue pair (tuple) for
     // each field
@@ -75,7 +73,7 @@ pub fn derive_into_robj(item: TokenStream) -> TokenStream {
     }
 
     // The only thing we emit from this macro is the conversion trait impl
-    TokenStream::from(quote!(
+    Ok(TokenStream::from(quote!(
         impl std::convert::From<&#struct_name> for Robj {
             fn from(value: &#struct_name) -> Self {
                 extendr_api::List::from_pairs([#(#tokens),*]).into()
@@ -86,5 +84,5 @@ pub fn derive_into_robj(item: TokenStream) -> TokenStream {
                 extendr_api::List::from_pairs([#(#tokens),*]).into()
             }
         }
-    ))
+    )))
 }
diff --git a/extendr-macros/src/wrappers.rs b/extendr-macros/src/wrappers.rs
index e78578726..890b62ef6 100644
--- a/extendr-macros/src/wrappers.rs
+++ b/extendr-macros/src/wrappers.rs
@@ -1,3 +1,28 @@
+//! This is responsible for generating the C functions that act as wrappers of
+//! the exported Rust functions.
+//!
+//! extendr relies on the [`.Call`-interface](https://cran.r-project.org/doc/manuals/R-exts.html#Calling-_002eCall)
+//! In short, it is necessary the the signature of the C-function have [`SEXP`]
+//! as the type for return type, and argument types.
+//!
+//! For instance, if your function returns nothing, the return type is not
+//! allowed to be `void`, instead `SEXP` must be used, and one should return
+//! [`R_NilValue`].
+//!
+//! ## R wrappers
+//!
+//! Within R, you may call `rextendr::document()` to generate R functions,
+//! that use the `.Call`-interface, to call the wrapped Rust functions.
+//!
+//! You may also manually implement these wrappers, in order to do special
+//! type-checking, or other annotation, that could be more convenient to do
+//! on the R-side. The C-functions are named according to `"{WRAP_PREFIX}{prefix}{mod_name}"`.
+//! See [`WRAP_PREFIX`], and note that `prefix` is set specifically for methods in
+//! `extendr`-impl blocks, while for functions have no prefix.
+//!
+//! [`R_NilValue`]: ::libR_sys::R_NilValue
+//! [`SEXP`]: ::libR_sys::SEXP
+
 use proc_macro2::Ident;
 use quote::{format_ident, quote};
 use syn::{parse_quote, punctuated::Punctuated, Expr, ExprLit, FnArg, ItemFn, Token, Type};
@@ -21,7 +46,7 @@ pub fn make_function_wrappers(
     attrs: &[syn::Attribute],
     sig: &mut syn::Signature,
     self_ty: Option<&syn::Type>,
-) {
+) -> syn::Result<()> {
     let rust_name = sig.ident.clone();
 
     let r_name_str = if let Some(r_name) = opts.r_name.as_ref() {
@@ -50,7 +75,7 @@ pub fn make_function_wrappers(
 
     let call_name = if has_self {
         let is_mut = match inputs.iter().next() {
-            Some(FnArg::Receiver(ref reciever)) => reciever.mutability.is_some(),
+            Some(FnArg::Receiver(ref receiver)) => receiver.mutability.is_some(),
             _ => false,
         };
         if is_mut {
@@ -72,12 +97,15 @@ pub fn make_function_wrappers(
         quote! { #rust_name }
     };
 
-    let formal_args: Punctuated<FnArg, Token![,]> = inputs
+    let formal_args = inputs
         .iter()
         .map(|input| translate_formal(input, self_ty))
-        .collect();
+        .collect::<syn::Result<Punctuated<FnArg, Token![,]>>>()?;
 
-    let convert_args: Vec<syn::Stmt> = inputs.iter().map(translate_to_robj).collect();
+    let convert_args: Vec<syn::Stmt> = inputs
+        .iter()
+        .map(translate_to_robj)
+        .collect::<syn::Result<Vec<syn::Stmt>>>()?;
 
     let actual_args: Punctuated<Expr, Token![,]> = inputs
         .iter()
@@ -87,7 +115,7 @@ pub fn make_function_wrappers(
     let meta_args: Vec<Expr> = inputs
         .iter_mut()
         .map(|input| translate_meta_arg(input, self_ty))
-        .collect();
+        .collect::<syn::Result<Vec<Expr>>>()?;
 
     // Generate wrappers for rust functions to be called from R.
     // Example:
@@ -117,6 +145,48 @@ pub fn make_function_wrappers(
             });)
         })
         .unwrap_or_default();
+
+    // figure out if
+    // -> &Self
+    // -> &mut Self
+    // Or if instead of `Self` the type name is used directly
+    // -> &ImplType / &mut ImplType
+    let return_is_ref_self = {
+        match sig.output {
+            // matches -> () or no-return type
+            syn::ReturnType::Default => false,
+            // ignoring the `-> Self` or `-> ImplType`, as that is not a Reference-type
+            // matches -> &T or &mut T
+            syn::ReturnType::Type(_, ref return_type) => match return_type.as_ref() {
+                Type::Reference(ref reference_type) => {
+                    // checks if T is Self or explicit impl type name
+                    if let Type::Path(path) = reference_type.elem.as_ref() {
+                        let is_typename_impl_type = self_ty
+                            .map(|x| x == reference_type.elem.as_ref())
+                            .unwrap_or(false);
+                        path.path.is_ident("Self") || is_typename_impl_type
+                    } else {
+                        false
+                    }
+                }
+                _ => false,
+            },
+        }
+    };
+
+    let return_type_conversion = if return_is_ref_self {
+        // instead of converting &Self / &mut Self, pass on the passed
+        // ExternalPtr<Self>
+        quote!(
+            let _return_ref_to_self = #call_name(#actual_args);
+            //FIXME: find a less hardcoded way to write `_self_robj`
+            Ok(_self_robj)
+        )
+    } else {
+        quote!(Ok(extendr_api::Robj::from(#call_name(#actual_args))))
+    };
+
+    // TODO: the unsafe in here is unnecessary
     wrappers.push(parse_quote!(
         #[no_mangle]
         #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
@@ -132,7 +202,7 @@ pub fn make_function_wrappers(
             > = unsafe {
                 #( #convert_args )*
                 std::panic::catch_unwind(||-> std::result::Result<Robj, extendr_api::Error> {
-                    Ok(extendr_api::Robj::from(#call_name(#actual_args)))
+                    #return_type_conversion
                 })
             };
 
@@ -144,7 +214,7 @@ pub fn make_function_wrappers(
                 Ok(Ok(zz)) => {
                     return unsafe { zz.get() };
                 }
-                // any conversion error bubbled from #actual_args conversions of incomming args from R.
+                // any conversion error bubbled from #actual_args conversions of incoming args from R.
                 Ok(Err(conversion_err)) => {
                     let err_string = conversion_err.to_string();
                     drop(conversion_err); // try_from=true errors contain Robj, this must be dropped to not leak
@@ -187,6 +257,8 @@ pub fn make_function_wrappers(
             })
         }
     ));
+
+    Ok(())
 }
 
 // Extract doc strings from attributes.
@@ -236,7 +308,7 @@ pub fn mangled_type_name(type_: &Type) -> String {
     res
 }
 
-// Return a simplified type name that will be meaningful to R. Defaults to a digest.
+/// Return a simplified type name that will be meaningful to R. Defaults to a digest.
 // For example:
 // & Fred -> Fred
 // * Fred -> Fred
@@ -265,28 +337,33 @@ pub fn type_name(type_: &Type) -> String {
 }
 
 // Generate a list of arguments for the wrapper. All arguments are SEXP for .Call in R.
-pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> FnArg {
+pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> syn::Result<FnArg> {
     match input {
         // function argument.
         FnArg::Typed(ref pattype) => {
             let pat = &pattype.pat.as_ref();
-            parse_quote! { #pat : extendr_api::SEXP }
+            Ok(parse_quote! { #pat : extendr_api::SEXP })
         }
-        // &self
-        FnArg::Receiver(ref reciever) => {
-            if !reciever.attrs.is_empty() || reciever.reference.is_none() {
-                panic!("expected &self or &mut self");
+        // &self / &mut self
+        FnArg::Receiver(ref receiver) => {
+            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
+                return Err(syn::Error::new_spanned(
+                    input,
+                    "expected &self or &mut self",
+                ));
             }
             if self_ty.is_none() {
-                panic!("found &self in non-impl function - have you missed the #[extendr] before the impl?");
+                return Err(syn::Error::new_spanned(
+                    input,"found &self in non-impl function - have you missed the #[extendr] before the impl?"
+                ));
             }
-            parse_quote! { _self : extendr_api::SEXP }
+            Ok(parse_quote! { _self : extendr_api::SEXP })
         }
     }
 }
 
 // Generate code to make a metadata::Arg.
-fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> Expr {
+fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> syn::Result<Expr> {
     match input {
         // function argument.
         FnArg::Typed(ref mut pattype) => {
@@ -299,49 +376,61 @@ fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> Expr {
             } else {
                 quote!(None)
             };
-            parse_quote! {
+            Ok(parse_quote! {
                 extendr_api::metadata::Arg {
                     name: #name_string,
                     arg_type: #type_string,
                     default: #default
                 }
-            }
+            })
         }
         // &self
-        FnArg::Receiver(ref reciever) => {
-            if !reciever.attrs.is_empty() || reciever.reference.is_none() {
-                panic!("expected &self or &mut self");
+        FnArg::Receiver(ref receiver) => {
+            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
+                return Err(syn::Error::new_spanned(
+                    input,
+                    "expected &self or &mut self",
+                ));
             }
             if self_ty.is_none() {
-                panic!("found &self in non-impl function - have you missed the #[extendr] before the impl?");
+                return Err(syn::Error::new_spanned(
+                    input,
+            "found &self in non-impl function - have you missed the #[extendr] before the impl?"
+        )
+    );
             }
             let type_string = type_name(self_ty.unwrap());
-            parse_quote! {
+            Ok(parse_quote! {
                 extendr_api::metadata::Arg {
                     name: "self",
                     arg_type: #type_string,
                     default: None
                 }
-            }
+            })
         }
     }
 }
 
 /// Convert `SEXP` arguments into `Robj`.
 /// This maintains the lifetime of references.
-fn translate_to_robj(input: &FnArg) -> syn::Stmt {
+///
+/// These conversions are from R into Rust
+fn translate_to_robj(input: &FnArg) -> syn::Result<syn::Stmt> {
     match input {
         FnArg::Typed(ref pattype) => {
             let pat = &pattype.pat.as_ref();
             if let syn::Pat::Ident(ref ident) = pat {
                 let varname = format_ident!("_{}_robj", ident.ident);
-                parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#pat); }
+                Ok(parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#pat); })
             } else {
-                panic!("expect identifier as arg name")
+                Err(syn::Error::new_spanned(
+                    input,
+                    "expect identifier as arg name",
+                ))
             }
         }
         FnArg::Receiver(_) => {
-            parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); }
+            Ok(parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); })
         }
     }
 }
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index a67d44eb9..519396b62 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -11,5 +11,5 @@ publish = false
 
 [dependencies]
 clap = { version = "4.4.6", features = ["derive"] }
-toml_edit = "0.22.6"
+toml_edit = "0.22"
 xshell = "0.2.5"
diff --git a/xtask/README.md b/xtask/README.md
index 11af2744d..868d15c6d 100644
--- a/xtask/README.md
+++ b/xtask/README.md
@@ -17,10 +17,12 @@ Usage: xtask <COMMAND>
 
 Commands:
   check-fmt      Run cargo fmt on extendr
+  fmt            Run `cargo fmt` on extendr crates
   r-cmd-check    Run R CMD check on {extendrtests}
   doc            Generate documentation for all features
   msrv           Check that the specified rust-version is MSRV
   devtools-test  Run devtools::test() on {extendrtests}
+  document       Generate wrappers with `rextendr::document()`
   help           Print this message or the help of the given subcommand(s)
 
 Options:
@@ -35,9 +37,9 @@ Let's describe some of the features listed above.
 This checks if the rust code follows the specified `rustfmt`. It does not
 format the code. In order to do so, please run `cargo fmt`.
 
-## `devtools-test`
+## `fmt`
 
-This performs `devtools::test()` in R, within the R-package `tests/extendrtests`.
+This command calls `cargo fmt` within the workspace, ensuring that the contents of `tests/extendrtests/src/rust` folder are formatted as well.
 
 ## `r-cmd-check`
 
@@ -48,6 +50,19 @@ Runs `R CMD check` tests in `tests/extendrtests`.
 Generates documentation as seen on [/extendr.github.io](https://extendr.github.io/extendr/extendr_api/), meaning it will enable feature `full-functionality`,
 which includes all the optional dependencies, plus all the additive features.
 
+## `msrv`
+
+Performs Minimum Supported Rust Version (MSRV) check in the repo.
+
+## `devtools-test`
+
+This performs `devtools::test()` in R, within the R-package `tests/extendrtests`. If this call results in messages about updating
+macro snapshots, one may run `cargo extendr devtools-test -a` to accept the newly generated snapshots.
+
+## `document`
+
+Use `cargo extendr document` to regenerate the wrappers for the integration-test package `tests/extendrtests`. This invokes `rextendr::document()`.
+
 ## TODO
 
 In the following are features that could be added to `xtask`.
diff --git a/xtask/src/cli/mod.rs b/xtask/src/cli/mod.rs
index a074082b0..a293479ad 100644
--- a/xtask/src/cli/mod.rs
+++ b/xtask/src/cli/mod.rs
@@ -1,9 +1,11 @@
 use clap::{Parser, Subcommand};
 
-use crate::cli::r_cmd_check::RCmdCheckArg;
-
+pub(crate) mod devtools_test;
 pub(crate) mod r_cmd_check;
 
+use devtools_test::DevtoolsTestArg;
+use r_cmd_check::RCmdCheckArg;
+
 #[derive(Parser, Debug)]
 #[command(author, version, about, long_about = None)]
 pub(crate) struct Cli {
@@ -15,6 +17,8 @@ pub(crate) struct Cli {
 pub(crate) enum Commands {
     #[command(about = "Run cargo fmt on extendr")]
     CheckFmt,
+    #[command(about = "Run `cargo fmt` on extendr crates")]
+    Fmt,
     #[command(about = "Run R CMD check on {extendrtests}")]
     RCmdCheck(RCmdCheckArg),
     #[command(about = "Generate documentation for all features")]
@@ -22,7 +26,9 @@ pub(crate) enum Commands {
     #[command(about = "Check that the specified rust-version is MSRV")]
     Msrv,
     #[command(about = "Run devtools::test() on {extendrtests}")]
-    DevtoolsTest,
+    DevtoolsTest(DevtoolsTestArg),
+    #[command(about = "Generate wrappers by `rextendr::document()`")]
+    Document,
 }
 
 pub(crate) fn parse() -> Cli {
diff --git a/xtask/src/cli/r_cmd_check.rs b/xtask/src/cli/r_cmd_check.rs
index 6afa4fead..eead2eff2 100644
--- a/xtask/src/cli/r_cmd_check.rs
+++ b/xtask/src/cli/r_cmd_check.rs
@@ -24,9 +24,9 @@ pub(crate) enum ErrorOn {
     Error,
 }
 
-impl Into<RCmdCheckErrorOn> for ErrorOn {
-    fn into(self) -> RCmdCheckErrorOn {
-        match self {
+impl From<ErrorOn> for RCmdCheckErrorOn {
+    fn from(val: ErrorOn) -> Self {
+        match val {
             ErrorOn::Never => RCmdCheckErrorOn::Never,
             ErrorOn::Note => RCmdCheckErrorOn::Note,
             ErrorOn::Warning => RCmdCheckErrorOn::Warning,
diff --git a/xtask/src/commands/cargo_fmt.rs b/xtask/src/commands/cargo_fmt.rs
new file mode 100644
index 000000000..4a499a153
--- /dev/null
+++ b/xtask/src/commands/cargo_fmt.rs
@@ -0,0 +1,14 @@
+use xshell::{cmd, Error, Shell};
+
+pub(crate) fn run(shell: &Shell) -> Result<(), Error> {
+    // extendr-api, extendr-macros, extendr-engine, xtask
+    cmd!(shell, "cargo fmt --all").run()?;
+    // extendrtests
+    cmd!(
+        shell,
+        "cargo fmt --all --manifest-path tests/extendrtests/src/rust/Cargo.toml"
+    )
+    .run()?;
+
+    Ok(())
+}
diff --git a/xtask/src/commands/cargo_fmt_check.rs b/xtask/src/commands/cargo_fmt_check.rs
index ca0df58fd..ebcbd0714 100644
--- a/xtask/src/commands/cargo_fmt_check.rs
+++ b/xtask/src/commands/cargo_fmt_check.rs
@@ -1,12 +1,19 @@
 use xshell::{cmd, Error, Shell};
 
 pub(crate) fn run(shell: &Shell) -> Result<(), Error> {
-    let check_result = cmd!(shell, "cargo fmt -- --check").run();
-    if check_result.is_err() {
-        println!("Please run `cargo fmt --all`");
+    let check_extendr = cmd!(shell, "cargo fmt --all -- --check").run();
+
+    let check_extendrtests = cmd!(
+        shell,
+        "cargo fmt --all --manifest-path tests/extendrtests/src/rust/Cargo.toml -- --check"
+    )
+    .run();
+
+    if check_extendr.is_err() || check_extendrtests.is_err() {
+        println!("Please run `cargo extendr fmt`");
     } else {
         println!("Success!");
     }
 
-    check_result
+    check_extendr
 }
diff --git a/xtask/src/commands/generate_docs.rs b/xtask/src/commands/generate_docs.rs
index 2c4fec48f..62c427060 100644
--- a/xtask/src/commands/generate_docs.rs
+++ b/xtask/src/commands/generate_docs.rs
@@ -1,7 +1,8 @@
 use xshell::{cmd, Error, Shell};
 
+/// Generates documentation like on the site extendr.github.io
 pub(crate) fn run(shell: &Shell) -> Result<(), Error> {
-    let _generate_docs = cmd!(
+    cmd!(
         shell,
         "cargo doc --workspace --no-deps --document-private-items --features full-functionality"
     )
diff --git a/xtask/src/commands/mod.rs b/xtask/src/commands/mod.rs
index 1c20dbbe6..39952e1fc 100644
--- a/xtask/src/commands/mod.rs
+++ b/xtask/src/commands/mod.rs
@@ -1,5 +1,7 @@
+pub(crate) mod cargo_fmt;
 pub(crate) mod cargo_fmt_check;
 pub(crate) mod cargo_msrv;
 pub(crate) mod devtools_test;
 pub(crate) mod generate_docs;
 pub(crate) mod r_cmd_check;
+pub(crate) mod rextendr_document;
diff --git a/xtask/src/commands/rextendr_document.rs b/xtask/src/commands/rextendr_document.rs
new file mode 100644
index 000000000..0effcd4c1
--- /dev/null
+++ b/xtask/src/commands/rextendr_document.rs
@@ -0,0 +1,55 @@
+//! This invokes `rextendr::document()` within `tests/extendrtests`.
+//!
+//! It uses the vendored `rextendr` in the repository as the source package.
+//!
+//! 1. Ensure that `git submodule update --init` was invoked once, as to setup
+//! the vendored `rextendr` package.
+//! 2. `devtools` must be installed on system.
+//!
+//!
+//! The idea here is to be able to develop `rextendr` alongside `extendr`,
+//! as well as ease the development of extendr.
+//!
+use std::error::Error;
+
+use crate::extendrtests::with_absolute_path::{swap_extendr_api_path, R_FOLDER_PATH};
+use xshell::{cmd, Shell};
+
+pub(crate) fn run(shell: &Shell) -> Result<(), Box<dyn Error>> {
+    let _document_handle = swap_extendr_api_path(shell)?;
+
+    run_rextendr_document(shell)
+}
+
+fn run_rextendr_document(shell: &Shell) -> Result<(), Box<dyn Error>> {
+    let _r_path = shell.push_dir(R_FOLDER_PATH);
+
+    let rextendr_submodule = std::path::Path::new(".../../rextendr");
+    let rextendr_submodule = matches!(rextendr_submodule.try_exists(), Ok(true));
+    if rextendr_submodule {
+        println!("Loading vendored `{{rextendr}}`");
+        cmd!(shell, "Rscript")
+            .args([
+                "-e",
+                r#"requireNamespace("devtools")"#,
+                "-e",
+                r#"devtools::load_all("../../rextendr")"#,
+                "-e",
+                r#"rextendr::document()"#,
+            ])
+            .run()?;
+    } else {
+        // check if rextendr is installed and use that instead
+        println!("Using installed `{{rextendr}}`");
+        cmd!(shell, "Rscript")
+            .args([
+                "-e",
+                r#"requireNamespace("rextendr")"#,
+                "-e",
+                r#"rextendr::document()"#,
+            ])
+            .run()?;
+    }
+
+    Ok(())
+}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index a8a146278..b7085b617 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -21,6 +21,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             .canonicalize()?,
     );
     match cli.command {
+        cli::Commands::Fmt => commands::cargo_fmt::run(&shell)?,
         cli::Commands::CheckFmt => commands::cargo_fmt_check::run(&shell)?,
         cli::Commands::RCmdCheck(RCmdCheckArg {
             no_build_vignettes,
@@ -35,7 +36,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         )?,
         cli::Commands::Doc => commands::generate_docs::run(&shell)?,
         cli::Commands::Msrv => commands::cargo_msrv::run(&shell)?,
-        cli::Commands::DevtoolsTest => commands::devtools_test::run(&shell)?,
+        cli::Commands::DevtoolsTest(args) => commands::devtools_test::run(&shell, args)?,
+        cli::Commands::Document => commands::rextendr_document::run(&shell)?,
     };
 
     Ok(())


Test patch:
diff --git a/extendr-api/tests/dataframe_tests.rs b/extendr-api/tests/dataframe_tests.rs
index 00c4eeedd..c52f50aed 100644
--- a/extendr-api/tests/dataframe_tests.rs
+++ b/extendr-api/tests/dataframe_tests.rs
@@ -51,3 +51,64 @@ fn test_into_robj_dataframe() {
 
     }
 }
+
+#[derive(IntoDataFrameRow)]
+struct Row {
+    name: u32,
+}
+
+#[extendr]
+fn dataframe_conversion(_data_frame: Dataframe<Row>) -> Robj {
+    vec![Row { name: 42 }].into_dataframe().unwrap().into_robj()
+}
+
+#[extendr(use_try_from = true)]
+fn dataframe_conversion_try_from(_data_frame: Dataframe<Row>) -> Robj {
+    vec![Row { name: 42 }].into_dataframe().unwrap().into_robj()
+}
+
+#[extendr]
+fn return_dataframe(_data_frame: Dataframe<Row>) -> Dataframe<Row> {
+    vec![Row { name: 42 }].into_dataframe().unwrap()
+}
+
+#[extendr(use_try_from = true)]
+fn return_dataframe_try_from(_data_frame: Dataframe<Row>) -> Dataframe<Row> {
+    vec![Row { name: 42 }].into_dataframe().unwrap()
+}
+
+#[test]
+fn test_storing_external_ptr_as_row() {
+    struct Row {
+        _id: u32,
+    }
+
+    #[extendr]
+    impl Row {}
+
+    #[extendr]
+    fn use_dataframe_exptr(data_frame: Dataframe<Row>) -> Dataframe<Row> {
+        data_frame
+    }
+}
+
+#[test]
+fn test_dataframe_row_and_externalptr() {
+    #[derive(IntoDataFrameRow)]
+    struct RowAndExptr {
+        id: u32,
+    }
+
+    #[extendr]
+    impl RowAndExptr {}
+
+    #[extendr]
+    fn use_dataframe_exptr2(data_frame: Dataframe<RowAndExptr>) -> Dataframe<RowAndExptr> {
+        data_frame
+    }
+
+    #[extendr(use_try_from = true)]
+    fn use_dataframe_exptr3(data_frame: Dataframe<RowAndExptr>) -> Dataframe<RowAndExptr> {
+        data_frame
+    }
+}
diff --git a/extendr-macros/tests/cases/case-extendr-macro.rs b/extendr-macros/tests/cases/case-extendr-macro.rs
new file mode 100644
index 000000000..86117d78e
--- /dev/null
+++ b/extendr-macros/tests/cases/case-extendr-macro.rs
@@ -0,0 +1,68 @@
+use extendr_macros::extendr;
+use extendr_macros::{IntoRobj, TryFromRobj};
+
+#[extendr(foo = true)]
+fn foo() {}
+
+#[extendr(use_try_from = 1)]
+fn foo() {}
+
+#[extendr(r_name = 1)]
+fn foo() {}
+
+#[extendr(mod_name = 1)]
+fn foo() {}
+
+#[extendr(use_rng = 1)]
+fn foo() {}
+
+// impl -----------------------------------------
+
+struct FooStruct {}
+
+#[extendr]
+impl FooStruct {
+    fn nonref_self(self) {}
+}
+
+impl FooStruct {
+    #[extendr]
+    fn misplaced_macro(&self) {}
+}
+
+#[extendr]
+default impl FooStruct {}
+
+#[extendr]
+unsafe impl FooStruct {}
+
+#[extendr]
+impl<const N: usize> FooStruct {}
+
+struct FooStructWithParam<A> {
+    a: A,
+}
+
+#[extendr]
+impl<A> FooStructWithParam<A> {}
+
+#[extendr]
+impl FooStructWithParam<A> where A: usize {}
+
+// derive ---------------------------------------
+
+#[derive(TryFromRobj)]
+enum FooEnum1 {
+    A,
+    B,
+    C,
+}
+
+#[derive(IntoRobj)]
+enum FooEnum2 {
+    A,
+    B,
+    C,
+}
+
+fn main() {}
diff --git a/extendr-macros/tests/cases/case-extendr-macro.stderr b/extendr-macros/tests/cases/case-extendr-macro.stderr
new file mode 100644
index 000000000..c63849b02
--- /dev/null
+++ b/extendr-macros/tests/cases/case-extendr-macro.stderr
@@ -0,0 +1,99 @@
+error: Unexpected key
+ --> tests/cases/case-extendr-macro.rs:4:11
+  |
+4 | #[extendr(foo = true)]
+  |           ^^^
+
+error: unexpected end of input, `use_try_from` must be `true` or `false`
+ --> tests/cases/case-extendr-macro.rs:7:1
+  |
+7 | #[extendr(use_try_from = 1)]
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the attribute macro `extendr` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unexpected end of input, `r_name` must be a string literal
+  --> tests/cases/case-extendr-macro.rs:10:1
+   |
+10 | #[extendr(r_name = 1)]
+   | ^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `extendr` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unexpected end of input, `mod_name` must be a string literal
+  --> tests/cases/case-extendr-macro.rs:13:1
+   |
+13 | #[extendr(mod_name = 1)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `extendr` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unexpected end of input, `use_rng` must be `true` or `false`
+  --> tests/cases/case-extendr-macro.rs:16:1
+   |
+16 | #[extendr(use_rng = 1)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `extendr` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: expected &self or &mut self
+  --> tests/cases/case-extendr-macro.rs:25:20
+   |
+25 |     fn nonref_self(self) {}
+   |                    ^^^^
+
+error: found &self in non-impl function - have you missed the #[extendr] before the impl?
+  --> tests/cases/case-extendr-macro.rs:30:24
+   |
+30 |     fn misplaced_macro(&self) {}
+   |                        ^^^^^
+
+error: default not allowed in #[extendr] impl
+  --> tests/cases/case-extendr-macro.rs:34:1
+   |
+34 | default impl FooStruct {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unsafe not allowed in #[extendr] impl
+  --> tests/cases/case-extendr-macro.rs:37:1
+   |
+37 | unsafe impl FooStruct {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: const params not allowed in #[extendr] impl
+  --> tests/cases/case-extendr-macro.rs:40:1
+   |
+40 | impl<const N: usize> FooStruct {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: type params not allowed in #[extendr] impl
+  --> tests/cases/case-extendr-macro.rs:47:1
+   |
+47 | impl<A> FooStructWithParam<A> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: where clause not allowed in #[extendr] impl
+  --> tests/cases/case-extendr-macro.rs:50:1
+   |
+50 | impl FooStructWithParam<A> where A: usize {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: Only struct is supported
+  --> tests/cases/case-extendr-macro.rs:55:1
+   |
+55 | / enum FooEnum1 {
+56 | |     A,
+57 | |     B,
+58 | |     C,
+59 | | }
+   | |_^
+
+error: Only struct is supported
+  --> tests/cases/case-extendr-macro.rs:62:1
+   |
+62 | / enum FooEnum2 {
+63 | |     A,
+64 | |     B,
+65 | |     C,
+66 | | }
+   | |_^
diff --git a/extendr-macros/tests/extendr_impl/extendr_impl_fail.rs b/extendr-macros/tests/extendr_impl/extendr_impl_fail.rs
new file mode 100644
index 000000000..d5c5a9281
--- /dev/null
+++ b/extendr-macros/tests/extendr_impl/extendr_impl_fail.rs
@@ -0,0 +1,40 @@
+#![allow(unused_imports)]
+
+#[allow(unused_imports)]
+use extendr_api::prelude::*;
+
+struct Foo;
+
+#[extendr]
+impl Foo {
+    fn take_ownership(self) {}
+}
+
+struct Foo2;
+
+#[extendr]
+impl Foo2 {
+    fn take_ownership(self: Self) {}
+}
+
+struct Foo3;
+
+#[extendr]
+impl Foo3 {
+    fn take_ownership(self: Foo3) {}
+}
+struct Foo4;
+
+#[extendr]
+impl Foo4 {
+    fn return_ownership(self) -> Self {}
+}
+
+struct Foo5;
+
+#[extendr]
+impl Foo5 {
+    fn take_ownership(self: Self) -> Foo5 {}
+}
+
+fn main() {}
diff --git a/extendr-macros/tests/extendr_impl/extendr_impl_fail.stderr b/extendr-macros/tests/extendr_impl/extendr_impl_fail.stderr
new file mode 100644
index 000000000..a43cf4394
--- /dev/null
+++ b/extendr-macros/tests/extendr_impl/extendr_impl_fail.stderr
@@ -0,0 +1,29 @@
+error: expected &self or &mut self
+  --> tests/extendr_impl/extendr_impl_fail.rs:10:23
+   |
+10 |     fn take_ownership(self) {}
+   |                       ^^^^
+
+error: expected &self or &mut self
+  --> tests/extendr_impl/extendr_impl_fail.rs:17:23
+   |
+17 |     fn take_ownership(self: Self) {}
+   |                       ^^^^^^^^^^
+
+error: expected &self or &mut self
+  --> tests/extendr_impl/extendr_impl_fail.rs:24:23
+   |
+24 |     fn take_ownership(self: Foo3) {}
+   |                       ^^^^^^^^^^
+
+error: expected &self or &mut self
+  --> tests/extendr_impl/extendr_impl_fail.rs:30:25
+   |
+30 |     fn return_ownership(self) -> Self {}
+   |                         ^^^^
+
+error: expected &self or &mut self
+  --> tests/extendr_impl/extendr_impl_fail.rs:37:23
+   |
+37 |     fn take_ownership(self: Self) -> Foo5 {}
+   |                       ^^^^^^^^^^
diff --git a/extendr-macros/tests/trybuild.rs b/extendr-macros/tests/trybuild.rs
new file mode 100644
index 000000000..8fb0270fc
--- /dev/null
+++ b/extendr-macros/tests/trybuild.rs
@@ -0,0 +1,6 @@
+#[test]
+fn test_macro_failures() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/cases/*.rs");
+    t.compile_fail("tests/extendr_impl/extendr_impl_fail.rs");
+}
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index b20eb5c85..9940ee815 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -76,9 +76,33 @@ add_5_if_not_null <- function(x) .Call(wrap__add_5_if_not_null, x)
 #' @export
 my_device <- function(welcome_message) invisible(.Call(wrap__my_device, welcome_message))
 
-#' Return string `"Hello world!"` to R.
-#' @export
-hello_submodule <- function() .Call(wrap__hello_submodule)
+new_usize <- function(robj) .Call(wrap__new_usize, robj)
+
+tst_altstring <- function() .Call(wrap__tst_altstring)
+
+tst_altinteger <- function() .Call(wrap__tst_altinteger)
+
+test_derive_into_dataframe <- function() .Call(wrap__test_derive_into_dataframe)
+
+test_into_robj_dataframe <- function() .Call(wrap__test_into_robj_dataframe)
+
+leak_implicit_strings <- function(x) .Call(wrap__leak_implicit_strings, x)
+
+leak_implicit_doubles <- function(x) .Call(wrap__leak_implicit_doubles, x)
+
+leak_arg2_try_implicit_strings <- function(`_y`, x) .Call(wrap__leak_arg2_try_implicit_strings, `_y`, x)
+
+leak_arg2_try_implicit_doubles <- function(`_y`, x) .Call(wrap__leak_arg2_try_implicit_doubles, `_y`, x)
+
+leak_unwrap_strings <- function(x) .Call(wrap__leak_unwrap_strings, x)
+
+leak_unwrap_doubles <- function(x) .Call(wrap__leak_unwrap_doubles, x)
+
+leak_positive_control <- function(x) invisible(.Call(wrap__leak_positive_control, x))
+
+leak_negative_control <- function(x) invisible(.Call(wrap__leak_negative_control, x))
+
+type_aware_sum <- function(input) .Call(wrap__type_aware_sum, input)
 
 #' Calculate Euclidean distance matrix
 #' Test case adopted from https://github.com/mikemahoney218/examplerust/blob/23d21b1ced4e24b7a7c00dd36290114dc1bbd113/src/rust/src/lib.rs#L5
@@ -86,8 +110,6 @@ hello_submodule <- function() .Call(wrap__hello_submodule)
 #' @export
 euclidean_dist <- function(a) .Call(wrap__euclidean_dist, a)
 
-type_aware_sum <- function(input) .Call(wrap__type_aware_sum, input)
-
 #' Test raw identifiers (`r#`) in function arguments are parsed correctly.
 #' See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
 #' @param type : i32 or `NULL`
@@ -105,27 +127,9 @@ true <- function() .Call(wrap__true)
 #' @export
 false <- function(type) .Call(wrap__false, type)
 
-leak_implicit_strings <- function(x) .Call(wrap__leak_implicit_strings, x)
-
-leak_implicit_doubles <- function(x) .Call(wrap__leak_implicit_doubles, x)
-
-leak_arg2_try_implicit_strings <- function(`_y`, x) .Call(wrap__leak_arg2_try_implicit_strings, `_y`, x)
-
-leak_arg2_try_implicit_doubles <- function(`_y`, x) .Call(wrap__leak_arg2_try_implicit_doubles, `_y`, x)
-
-leak_unwrap_strings <- function(x) .Call(wrap__leak_unwrap_strings, x)
-
-leak_unwrap_doubles <- function(x) .Call(wrap__leak_unwrap_doubles, x)
-
-leak_positive_control <- function(x) invisible(.Call(wrap__leak_positive_control, x))
-
-leak_negative_control <- function(x) invisible(.Call(wrap__leak_negative_control, x))
-
-new_usize <- function(robj) .Call(wrap__new_usize, robj)
-
-tst_altstring <- function() .Call(wrap__tst_altstring)
-
-tst_altinteger <- function() .Call(wrap__tst_altinteger)
+#' Return string `"Hello world!"` to R.
+#' @export
+hello_submodule <- function() .Call(wrap__hello_submodule)
 
 #' Class for testing (exported)
 #' @examples
@@ -196,7 +200,15 @@ MySubmoduleClass$set_a <- function(x) invisible(.Call(wrap__MySubmoduleClass__se
 
 MySubmoduleClass$a <- function() .Call(wrap__MySubmoduleClass__a, self)
 
-MySubmoduleClass$me <- function() .Call(wrap__MySubmoduleClass__me, self)
+MySubmoduleClass$me_owned <- function() .Call(wrap__MySubmoduleClass__me_owned, self)
+
+MySubmoduleClass$me_ref <- function() .Call(wrap__MySubmoduleClass__me_ref, self)
+
+MySubmoduleClass$me_mut <- function() .Call(wrap__MySubmoduleClass__me_mut, self)
+
+MySubmoduleClass$me_explicit_ref <- function() .Call(wrap__MySubmoduleClass__me_explicit_ref, self)
+
+MySubmoduleClass$me_explicit_mut <- function() .Call(wrap__MySubmoduleClass__me_explicit_mut, self)
 
 #' @rdname MySubmoduleClass
 #' @usage NULL
diff --git a/tests/extendrtests/man/MySubmoduleClass.Rd b/tests/extendrtests/man/MySubmoduleClass.Rd
index 0e1fb8385..510402b4a 100644
--- a/tests/extendrtests/man/MySubmoduleClass.Rd
+++ b/tests/extendrtests/man/MySubmoduleClass.Rd
@@ -6,7 +6,7 @@
 \alias{$.MySubmoduleClass}
 \title{Class for testing (exported)}
 \format{
-An object of class \code{environment} of length 4.
+An object of class \code{environment} of length 8.
 }
 \usage{
 MySubmoduleClass
diff --git a/tests/extendrtests/src/rust/src/dataframe.rs b/tests/extendrtests/src/rust/src/dataframe.rs
new file mode 100644
index 000000000..fefacfc74
--- /dev/null
+++ b/tests/extendrtests/src/rust/src/dataframe.rs
@@ -0,0 +1,75 @@
+use extendr_api::prelude::*;
+
+#[derive(Debug, IntoDataFrameRow)]
+struct MyStruct {
+    x: Rint,
+    y: Rstr,
+}
+
+#[extendr]
+fn test_derive_into_dataframe() -> Dataframe<MyStruct> {
+    let v = vec![
+        MyStruct {
+            x: 0.into(),
+            y: "abc".into(),
+        },
+        MyStruct {
+            x: 1.into(),
+            y: "xyz".into(),
+        },
+    ];
+    v.into_dataframe().unwrap()
+}
+
+#[extendr]
+fn test_into_robj_dataframe() -> Robj {
+    let v = vec![
+        MyStruct {
+            x: 0.into(),
+            y: "abc".into(),
+        },
+        MyStruct {
+            x: 1.into(),
+            y: "xyz".into(),
+        },
+    ];
+    v.into_dataframe().unwrap().into_robj()
+}
+
+// Not possible today
+// https://github.com/extendr/extendr/issues/727
+// #[derive(Debug, IntoDataFrameRow)]
+// struct Val;
+
+// #[extendr]
+// impl Val {
+//     fn new() -> Self {
+//         Val {}
+//     }
+// }
+// #[derive(IntoDataFrameRow)]
+// struct Row {
+//     v: Val,
+// }
+// #[extendr]
+// fn test_use_externalptr() -> Robj {
+//     vec![Row { v: Val::new() }]
+//         .into_dataframe()
+//         .unwrap()
+//         .into_robj()
+// }
+
+// This isn't relavent right now
+// thats because issue https://github.com/extendr/extendr/issues/714
+// is not solved we would need an iterator for Dataframe<T> for it to
+// actually be useful
+// #[extendr(use_try_from = true)]
+// fn dataframe_conversion_try_from(_data_frame: Dataframe<Row>) -> Robj {
+//     vec![Row { name: 42 }].into_dataframe().unwrap().into_robj()
+// }
+
+extendr_module! {
+    mod dataframe;
+    fn test_derive_into_dataframe;
+    fn test_into_robj_dataframe;
+}
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index ae7e26dcc..5ea609d3d 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -1,18 +1,13 @@
 use extendr_api::{graphics::*, prelude::*};
 
-mod submodule;
-
-mod optional_ndarray;
-
+mod altrep;
+mod dataframe;
 mod graphic_device;
-
+mod memory_leaks;
 mod optional_either;
-
+mod optional_ndarray;
 mod raw_identifiers;
-
-mod memory_leaks;
-
-mod altrep;
+mod submodule;
 
 // Return string `"Hello world!"` to R.
 #[extendr]
@@ -360,10 +355,12 @@ extendr_module! {
 
     fn my_device;
 
-    use submodule;
-    use optional_ndarray;
+    use altrep;
+    use dataframe;
+    use memory_leaks;
     use optional_either;
+    use optional_ndarray;
     use raw_identifiers;
-    use memory_leaks;
-    use altrep;
+    use submodule;
+
 }
diff --git a/tests/extendrtests/src/rust/src/submodule.rs b/tests/extendrtests/src/rust/src/submodule.rs
index 6aa8e444c..4dd488ba7 100644
--- a/tests/extendrtests/src/rust/src/submodule.rs
+++ b/tests/extendrtests/src/rust/src/submodule.rs
@@ -8,7 +8,7 @@ fn hello_submodule() -> &'static str {
 }
 
 // Class for testing
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone, Copy)]
 struct MySubmoduleClass {
     a: i32,
 }
@@ -26,24 +26,50 @@ impl MySubmoduleClass {
     fn new() -> Self {
         Self { a: 0 }
     }
-    
+
     /// Method for setting stuff.
     /// @param x a number
-    fn set_a(& mut self, x: i32) {
+    fn set_a(&mut self, x: i32) {
         self.a = x;
     }
-    
+
     /// Method for getting stuff.
     fn a(&self) -> i32 {
         self.a
     }
-    
-    /// Method for getting one's self.
-    fn me(&self) -> &Self {
+
+    // NOTE: Cannot move ownership, as that concept is incompatible with bridging
+    // data from R to Rust
+    // fn myself(self) -> Self {
+    //     self
+    // }
+
+    /// Method for getting one's (by way of a copy) self.
+    fn me_owned(&self) -> Self {
+        // only possible due to `derive(Clone, Copy)`
+        *self
+    }
+
+    /// Method for getting one's (ref) self.
+    fn me_ref(&self) -> &Self {
+        self
+    }
+
+    /// Method for getting one's (ref mut) self.
+    fn me_mut(&mut self) -> &mut Self {
+        self
+    }
+
+    /// Method for getting one's ref (explicit) self.
+    fn me_explicit_ref(&self) -> &MySubmoduleClass {
         self
     }
-}
 
+    /// Method for getting one's ref mut (explicit) self.
+    fn me_explicit_mut(&mut self) -> &mut MySubmoduleClass {
+        self
+    }
+}
 
 // Macro to generate exports
 extendr_module! {
diff --git a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
index fb854e723..9e6df0f69 100644
--- a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
+++ b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
@@ -9,23 +9,126 @@
       #[macro_use]
       extern crate std;
       use extendr_api::{graphics::*, prelude::*};
-      mod submodule {
+      mod altrep {
           use extendr_api::prelude::*;
-          /// Return string `"Hello world!"` to R.
-          /// @export
-          fn hello_submodule() -> &'static str {
-              "Hello World!"
+          pub struct VecUsize(pub Vec<Option<usize>>);
+          #[automatically_derived]
+          impl ::core::fmt::Debug for VecUsize {
+              #[inline]
+              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                  ::core::fmt::Formatter::debug_tuple_field1_finish(f, "VecUsize", &&self.0)
+              }
+          }
+          #[automatically_derived]
+          impl ::core::clone::Clone for VecUsize {
+              #[inline]
+              fn clone(&self) -> VecUsize {
+                  VecUsize(::core::clone::Clone::clone(&self.0))
+              }
+          }
+          impl AltrepImpl for VecUsize {
+              fn length(&self) -> usize {
+                  self.0.len()
+              }
+          }
+          #[cfg(use_r_altlist)]
+          impl VecUsize {}
+          impl<'a> extendr_api::FromRobj<'a> for &VecUsize {
+              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+                  if robj.check_external_ptr_type::<VecUsize>() {
+                      #[allow(clippy::transmute_ptr_to_ref)]
+                      Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<VecUsize>()) })
+                  } else {
+                      Err("expected VecUsize")
+                  }
+              }
+          }
+          impl<'a> extendr_api::FromRobj<'a> for &mut VecUsize {
+              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+                  if robj.check_external_ptr_type::<VecUsize>() {
+                      #[allow(clippy::transmute_ptr_to_ref)]
+                      Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<VecUsize>()) })
+                  } else {
+                      Err("expected VecUsize")
+                  }
+              }
+          }
+          impl From<VecUsize> for Robj {
+              fn from(value: VecUsize) -> Self {
+                  unsafe {
+                      let ptr = Box::into_raw(Box::new(value));
+                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
+                      res.set_attrib(class_symbol(), "VecUsize").unwrap();
+                      res.register_c_finalizer(Some(__finalize__VecUsize));
+                      res
+                  }
+              }
+          }
+          impl<'a> From<&'a VecUsize> for Robj {
+              fn from(value: &'a VecUsize) -> Self {
+                  unsafe {
+                      let ptr = Box::into_raw(Box::new(value));
+                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
+                      res.set_attrib(class_symbol(), "VecUsize").unwrap();
+                      res.register_c_finalizer(Some(__finalize__VecUsize));
+                      res
+                  }
+              }
+          }
+          extern "C" fn __finalize__VecUsize(sexp: extendr_api::SEXP) {
+              unsafe {
+                  let robj = extendr_api::robj::Robj::from_sexp(sexp);
+                  if robj.check_external_ptr_type::<VecUsize>() {
+                      let ptr = robj.external_ptr_addr::<VecUsize>();
+                      drop(Box::from_raw(ptr));
+                  }
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__VecUsize(impls: &mut Vec<extendr_api::metadata::Impl>) {
+              let mut methods = Vec::new();
+              impls
+                  .push(extendr_api::metadata::Impl {
+                      doc: "",
+                      name: "VecUsize",
+                      methods,
+                  });
+          }
+          #[cfg(use_r_altlist)]
+          impl AltListImpl for VecUsize {
+              fn elt(&self, index: usize) -> Robj {
+                  self.into_robj()
+              }
+          }
+          #[cfg(use_r_altlist)]
+          fn new_usize(robj: Integers) -> Altrep {
+              let x = robj
+                  .iter()
+                  .map(|x| match &x {
+                      _ if x.is_na() => None,
+                      _ if x.inner() < 0 => None,
+                      _ => Some(x.inner() as usize),
+                  })
+                  .collect();
+              let obj = VecUsize(x);
+              let class = Altrep::make_altlist_class::<VecUsize>("li", "mypkg");
+              Altrep::from_state_and_class(obj, class, false)
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__hello_submodule() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__new_usize(robj: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
+                  let _robj_robj = extendr_api::robj::Robj::from_sexp(robj);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(hello_submodule()))
+                      Ok(
+                          extendr_api::Robj::from(
+                              new_usize(<Integers>::from_robj(&_robj_robj)?),
+                          ),
+                      )
                   })
               };
               match wrap_result_state {
@@ -41,7 +144,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "hello_submodule"),
+                              format_args!("user function panicked: {0}", "new_usize"),
                           );
                           res
                       };
@@ -69,80 +172,82 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = ::alloc::vec::Vec::new();
+          fn meta__new_usize(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "robj",
+                          arg_type: "Integers",
+                          default: None,
+                      },
+                  ]),
+              );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Return string `\"Hello world!\"` to R.\n @export",
-                      rust_name: "hello_submodule",
-                      r_name: "hello_submodule",
-                      mod_name: "hello_submodule",
+                      doc: "",
+                      rust_name: "new_usize",
+                      r_name: "new_usize",
+                      mod_name: "new_usize",
                       args: args,
-                      return_type: "str",
-                      func_ptr: wrap__hello_submodule as *const u8,
+                      return_type: "Altrep",
+                      func_ptr: wrap__new_usize as *const u8,
                       hidden: false,
                   })
           }
-          struct MySubmoduleClass {
-              a: i32,
-          }
-          #[automatically_derived]
-          impl ::core::default::Default for MySubmoduleClass {
-              #[inline]
-              fn default() -> MySubmoduleClass {
-                  MySubmoduleClass {
-                      a: ::core::default::Default::default(),
-                  }
-              }
+          struct StringInts {
+              len: usize,
           }
           #[automatically_derived]
-          impl ::core::fmt::Debug for MySubmoduleClass {
+          impl ::core::fmt::Debug for StringInts {
               #[inline]
               fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                   ::core::fmt::Formatter::debug_struct_field1_finish(
                       f,
-                      "MySubmoduleClass",
-                      "a",
-                      &&self.a,
+                      "StringInts",
+                      "len",
+                      &&self.len,
                   )
               }
           }
-          /// Class for testing (exported)
-          /// @examples
-          /// x <- MySubmoduleClass$new()
-          /// x$a()
-          /// x$set_a(10)
-          /// x$a()
-          /// @export
-          impl MySubmoduleClass {
-              /// Method for making a new object.
-              fn new() -> Self {
-                  Self { a: 0 }
-              }
-              /// Method for setting stuff.
-              /// @param x a number
-              fn set_a(&mut self, x: i32) {
-                  self.a = x;
+          #[automatically_derived]
+          impl ::core::clone::Clone for StringInts {
+              #[inline]
+              fn clone(&self) -> StringInts {
+                  StringInts {
+                      len: ::core::clone::Clone::clone(&self.len),
+                  }
               }
-              /// Method for getting stuff.
-              fn a(&self) -> i32 {
-                  self.a
+          }
+          impl AltrepImpl for StringInts {
+              fn length(&self) -> usize {
+                  self.len as usize
               }
-              /// Method for getting one's self.
-              fn me(&self) -> &Self {
-                  self
+          }
+          impl AltStringImpl for StringInts {
+              fn elt(&self, index: usize) -> Rstr {
+                  {
+                      let res = ::alloc::fmt::format(format_args!("{0}", index));
+                      res
+                  }
+                      .into()
               }
           }
+          fn tst_altstring() -> Altrep {
+              let mystate = StringInts { len: 10 };
+              let class = Altrep::make_altstring_class::<StringInts>("si", "mypkg");
+              Altrep::from_state_and_class(mystate, class, false)
+          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__MySubmoduleClass__new() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__tst_altstring() -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(<MySubmoduleClass>::new()))
+                      Ok(extendr_api::Robj::from(tst_altstring()))
                   })
               };
               match wrap_result_state {
@@ -158,7 +263,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "new"),
+                              format_args!("user function panicked: {0}", "tst_altstring"),
                           );
                           res
                       };
@@ -186,47 +291,95 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__MySubmoduleClass__new(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__tst_altstring(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Method for making a new object.",
-                      rust_name: "new",
-                      r_name: "new",
-                      mod_name: "new",
+                      doc: "",
+                      rust_name: "tst_altstring",
+                      r_name: "tst_altstring",
+                      mod_name: "tst_altstring",
                       args: args,
-                      return_type: "Self",
-                      func_ptr: wrap__MySubmoduleClass__new as *const u8,
+                      return_type: "Altrep",
+                      func_ptr: wrap__tst_altstring as *const u8,
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__MySubmoduleClass__set_a(
-              _self: extendr_api::SEXP,
-              x: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              use extendr_api::robj::*;
-              let wrap_result_state: std::result::Result<
-                  std::result::Result<Robj, extendr_api::Error>,
-                  Box<dyn std::any::Any + Send>,
-              > = unsafe {
-                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              extendr_api::unwrap_or_throw(
-                                      <&mut MySubmoduleClass>::from_robj(&_self_robj),
-                                  )
-                                  .set_a(<i32>::from_robj(&_x_robj)?),
-                          ),
-                      )
-                  })
-              };
-              match wrap_result_state {
-                  Ok(Ok(zz)) => {
-                      return unsafe { zz.get() };
+          struct MyCompactIntRange {
+              start: i32,
+              len: i32,
+              step: i32,
+              missing_index: usize,
+          }
+          #[automatically_derived]
+          impl ::core::fmt::Debug for MyCompactIntRange {
+              #[inline]
+              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                  ::core::fmt::Formatter::debug_struct_field4_finish(
+                      f,
+                      "MyCompactIntRange",
+                      "start",
+                      &self.start,
+                      "len",
+                      &self.len,
+                      "step",
+                      &self.step,
+                      "missing_index",
+                      &&self.missing_index,
+                  )
+              }
+          }
+          #[automatically_derived]
+          impl ::core::clone::Clone for MyCompactIntRange {
+              #[inline]
+              fn clone(&self) -> MyCompactIntRange {
+                  MyCompactIntRange {
+                      start: ::core::clone::Clone::clone(&self.start),
+                      len: ::core::clone::Clone::clone(&self.len),
+                      step: ::core::clone::Clone::clone(&self.step),
+                      missing_index: ::core::clone::Clone::clone(&self.missing_index),
+                  }
+              }
+          }
+          impl AltrepImpl for MyCompactIntRange {
+              fn length(&self) -> usize {
+                  self.len as usize
+              }
+          }
+          impl AltIntegerImpl for MyCompactIntRange {
+              fn elt(&self, index: usize) -> Rint {
+                  if index == self.missing_index {
+                      Rint::na()
+                  } else {
+                      Rint::new(self.start + self.step * index as i32)
+                  }
+              }
+          }
+          fn tst_altinteger() -> Altrep {
+              let mystate = MyCompactIntRange {
+                  start: 0,
+                  len: 10,
+                  step: 1,
+                  missing_index: usize::MAX,
+              };
+              let class = Altrep::make_altinteger_class::<MyCompactIntRange>("cir", "mypkg");
+              Altrep::from_state_and_class(mystate, class.clone(), false)
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__tst_altinteger() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      Ok(extendr_api::Robj::from(tst_altinteger()))
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
                   }
                   Ok(Err(conversion_err)) => {
                       let err_string = conversion_err.to_string();
@@ -237,7 +390,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "set_a"),
+                              format_args!("user function panicked: {0}", "tst_altinteger"),
                           );
                           res
                       };
@@ -265,54 +418,195 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__MySubmoduleClass__set_a(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = <[_]>::into_vec(
-                  #[rustc_box]
-                  ::alloc::boxed::Box::new([
-                      extendr_api::metadata::Arg {
-                          name: "self",
-                          arg_type: "MySubmoduleClass",
-                          default: None,
-                      },
-                      extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "i32",
-                          default: None,
-                      },
-                  ]),
-              );
+          fn meta__tst_altinteger(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Method for setting stuff.\n @param x a number",
-                      rust_name: "set_a",
-                      r_name: "set_a",
-                      mod_name: "set_a",
+                      doc: "",
+                      rust_name: "tst_altinteger",
+                      r_name: "tst_altinteger",
+                      mod_name: "tst_altinteger",
                       args: args,
-                      return_type: "()",
-                      func_ptr: wrap__MySubmoduleClass__set_a as *const u8,
+                      return_type: "Altrep",
+                      func_ptr: wrap__tst_altinteger as *const u8,
                       hidden: false,
                   })
           }
           #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_altrep_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__new_usize(&mut functions);
+              meta__tst_altstring(&mut functions);
+              meta__tst_altinteger(&mut functions);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_altrep_metadata",
+                      mod_name: "get_altrep_metadata",
+                      r_name: "get_altrep_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_altrep_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_altrep_wrappers",
+                      mod_name: "make_altrep_wrappers",
+                      r_name: "make_altrep_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_altrep_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "altrep",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_altrep_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_altrep_metadata()).get() }
+          }
+          #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__MySubmoduleClass__a(
-              _self: extendr_api::SEXP,
+          pub extern "C" fn wrap__make_altrep_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_altrep_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_altrep_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_altrep_metadata()) };
+          }
+      }
+      mod dataframe {
+          use extendr_api::prelude::*;
+          struct MyStruct {
+              x: Rint,
+              y: Rstr,
+          }
+          #[automatically_derived]
+          impl ::core::fmt::Debug for MyStruct {
+              #[inline]
+              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                  ::core::fmt::Formatter::debug_struct_field2_finish(
+                      f,
+                      "MyStruct",
+                      "x",
+                      &self.x,
+                      "y",
+                      &&self.y,
+                  )
+              }
+          }
+          impl IntoDataFrameRow<MyStruct> for Vec<MyStruct> {
+              fn into_dataframe(self) -> Result<Dataframe<MyStruct>> {
+                  let mut x = Vec::with_capacity(self.len());
+                  let mut y = Vec::with_capacity(self.len());
+                  for val in self {
+                      x.push(val.x);
+                      y.push(val.y);
+                  }
+                  let caller = eval_string("data.frame")?;
+                  let res = caller
+                      .call(
+                          Pairlist::from_pairs(
+                              &[
+                                  ("x", extendr_api::robj::Robj::from(x)),
+                                  ("y", extendr_api::robj::Robj::from(y)),
+                              ],
+                          ),
+                      )?;
+                  res.try_into()
+              }
+          }
+          impl<I> IntoDataFrameRow<MyStruct> for (I,)
+          where
+              I: ExactSizeIterator<Item = MyStruct>,
+          {
+              /// Thanks to RFC 2451, we need to wrap a generic iterator in a tuple!
+              fn into_dataframe(self) -> Result<Dataframe<MyStruct>> {
+                  let mut x = Vec::with_capacity(self.0.len());
+                  let mut y = Vec::with_capacity(self.0.len());
+                  for val in self.0 {
+                      x.push(val.x);
+                      y.push(val.y);
+                  }
+                  let caller = eval_string("data.frame")?;
+                  let res = caller
+                      .call(
+                          Pairlist::from_pairs(
+                              &[
+                                  ("x", extendr_api::robj::Robj::from(x)),
+                                  ("y", extendr_api::robj::Robj::from(y)),
+                              ],
+                          ),
+                      )?;
+                  res.try_into()
+              }
+          }
+          fn test_derive_into_dataframe() -> Dataframe<MyStruct> {
+              let v = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      MyStruct {
+                          x: 0.into(),
+                          y: "abc".into(),
+                      },
+                      MyStruct {
+                          x: 1.into(),
+                          y: "xyz".into(),
+                      },
+                  ]),
+              );
+              v.into_dataframe().unwrap()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__test_derive_into_dataframe() -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              extendr_api::unwrap_or_throw(
-                                      <&MySubmoduleClass>::from_robj(&_self_robj),
-                                  )
-                                  .a(),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(test_derive_into_dataframe()))
                   })
               };
               match wrap_result_state {
@@ -328,7 +622,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "a"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "test_derive_into_dataframe",
+                              ),
                           );
                           res
                       };
@@ -356,49 +653,48 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__MySubmoduleClass__a(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = <[_]>::into_vec(
-                  #[rustc_box]
-                  ::alloc::boxed::Box::new([
-                      extendr_api::metadata::Arg {
-                          name: "self",
-                          arg_type: "MySubmoduleClass",
-                          default: None,
-                      },
-                  ]),
-              );
+          fn meta__test_derive_into_dataframe(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
+              let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Method for getting stuff.",
-                      rust_name: "a",
-                      r_name: "a",
-                      mod_name: "a",
+                      doc: "",
+                      rust_name: "test_derive_into_dataframe",
+                      r_name: "test_derive_into_dataframe",
+                      mod_name: "test_derive_into_dataframe",
                       args: args,
-                      return_type: "i32",
-                      func_ptr: wrap__MySubmoduleClass__a as *const u8,
+                      return_type: "Dataframe",
+                      func_ptr: wrap__test_derive_into_dataframe as *const u8,
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__MySubmoduleClass__me(
-              _self: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              use extendr_api::robj::*;
-              let wrap_result_state: std::result::Result<
+          fn test_into_robj_dataframe() -> Robj {
+              let v = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      MyStruct {
+                          x: 0.into(),
+                          y: "abc".into(),
+                      },
+                      MyStruct {
+                          x: 1.into(),
+                          y: "xyz".into(),
+                      },
+                  ]),
+              );
+              v.into_dataframe().unwrap().into_robj()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__test_into_robj_dataframe() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              extendr_api::unwrap_or_throw(
-                                      <&MySubmoduleClass>::from_robj(&_self_robj),
-                                  )
-                                  .me(),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(test_into_robj_dataframe()))
                   })
               };
               match wrap_result_state {
@@ -414,7 +710,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "me"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "test_into_robj_dataframe",
+                              ),
                           );
                           res
                       };
@@ -442,122 +741,44 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__MySubmoduleClass__me(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = <[_]>::into_vec(
-                  #[rustc_box]
-                  ::alloc::boxed::Box::new([
-                      extendr_api::metadata::Arg {
-                          name: "self",
-                          arg_type: "MySubmoduleClass",
-                          default: None,
-                      },
-                  ]),
-              );
+          fn meta__test_into_robj_dataframe(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Method for getting one's self.",
-                      rust_name: "me",
-                      r_name: "me",
-                      mod_name: "me",
+                      doc: "",
+                      rust_name: "test_into_robj_dataframe",
+                      r_name: "test_into_robj_dataframe",
+                      mod_name: "test_into_robj_dataframe",
                       args: args,
-                      return_type: "Self",
-                      func_ptr: wrap__MySubmoduleClass__me as *const u8,
+                      return_type: "Robj",
+                      func_ptr: wrap__test_into_robj_dataframe as *const u8,
                       hidden: false,
                   })
           }
-          impl<'a> extendr_api::FromRobj<'a> for &MySubmoduleClass {
-              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
-                      #[allow(clippy::transmute_ptr_to_ref)]
-                      Ok(unsafe {
-                          std::mem::transmute(robj.external_ptr_addr::<MySubmoduleClass>())
-                      })
-                  } else {
-                      Err("expected MySubmoduleClass")
-                  }
-              }
-          }
-          impl<'a> extendr_api::FromRobj<'a> for &mut MySubmoduleClass {
-              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
-                      #[allow(clippy::transmute_ptr_to_ref)]
-                      Ok(unsafe {
-                          std::mem::transmute(robj.external_ptr_addr::<MySubmoduleClass>())
-                      })
-                  } else {
-                      Err("expected MySubmoduleClass")
-                  }
-              }
-          }
-          impl From<MySubmoduleClass> for Robj {
-              fn from(value: MySubmoduleClass) -> Self {
-                  unsafe {
-                      let ptr = Box::into_raw(Box::new(value));
-                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
-                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
-                      res.register_c_finalizer(Some(__finalize__MySubmoduleClass));
-                      res
-                  }
-              }
-          }
-          impl<'a> From<&'a MySubmoduleClass> for Robj {
-              fn from(value: &'a MySubmoduleClass) -> Self {
-                  unsafe {
-                      let ptr = Box::into_raw(Box::new(value));
-                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
-                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
-                      res.register_c_finalizer(Some(__finalize__MySubmoduleClass));
-                      res
-                  }
-              }
-          }
-          extern "C" fn __finalize__MySubmoduleClass(sexp: extendr_api::SEXP) {
-              unsafe {
-                  let robj = extendr_api::robj::Robj::from_sexp(sexp);
-                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
-                      let ptr = robj.external_ptr_addr::<MySubmoduleClass>();
-                      drop(Box::from_raw(ptr));
-                  }
-              }
-          }
-          #[allow(non_snake_case)]
-          fn meta__MySubmoduleClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
-              let mut methods = Vec::new();
-              meta__MySubmoduleClass__new(&mut methods);
-              meta__MySubmoduleClass__set_a(&mut methods);
-              meta__MySubmoduleClass__a(&mut methods);
-              meta__MySubmoduleClass__me(&mut methods);
-              impls
-                  .push(extendr_api::metadata::Impl {
-                      doc: " Class for testing (exported)\n @examples\n x <- MySubmoduleClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
-                      name: "MySubmoduleClass",
-                      methods,
-                  });
-          }
           #[no_mangle]
           #[allow(non_snake_case)]
-          pub fn get_submodule_metadata() -> extendr_api::metadata::Metadata {
+          pub fn get_dataframe_metadata() -> extendr_api::metadata::Metadata {
               let mut functions = Vec::new();
               let mut impls = Vec::new();
-              meta__hello_submodule(&mut functions);
-              meta__MySubmoduleClass(&mut impls);
+              meta__test_derive_into_dataframe(&mut functions);
+              meta__test_into_robj_dataframe(&mut functions);
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Metadata access function.",
-                      rust_name: "get_submodule_metadata",
-                      mod_name: "get_submodule_metadata",
-                      r_name: "get_submodule_metadata",
+                      rust_name: "get_dataframe_metadata",
+                      mod_name: "get_dataframe_metadata",
+                      r_name: "get_dataframe_metadata",
                       args: Vec::new(),
                       return_type: "Metadata",
-                      func_ptr: wrap__get_submodule_metadata as *const u8,
+                      func_ptr: wrap__get_dataframe_metadata as *const u8,
                       hidden: true,
                   });
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Wrapper generator.",
-                      rust_name: "make_submodule_wrappers",
-                      mod_name: "make_submodule_wrappers",
-                      r_name: "make_submodule_wrappers",
+                      rust_name: "make_dataframe_wrappers",
+                      mod_name: "make_dataframe_wrappers",
+                      r_name: "make_dataframe_wrappers",
                       args: <[_]>::into_vec(
                           #[rustc_box]
                           ::alloc::boxed::Box::new([
@@ -574,24 +795,24 @@
                           ]),
                       ),
                       return_type: "String",
-                      func_ptr: wrap__make_submodule_wrappers as *const u8,
+                      func_ptr: wrap__make_dataframe_wrappers as *const u8,
                       hidden: true,
                   });
               extendr_api::metadata::Metadata {
-                  name: "submodule",
+                  name: "dataframe",
                   functions,
                   impls,
               }
           }
           #[no_mangle]
           #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_submodule_metadata() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__get_dataframe_metadata() -> extendr_api::SEXP {
               use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_submodule_metadata()).get() }
+              unsafe { extendr_api::Robj::from(get_dataframe_metadata()).get() }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_submodule_wrappers(
+          pub extern "C" fn wrap__make_dataframe_wrappers(
               use_symbols_sexp: extendr_api::SEXP,
               package_name_sexp: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
@@ -603,7 +824,7 @@
                   let robj = Robj::from_sexp(package_name_sexp);
                   let package_name: &str = <&str>::from_robj(&robj).unwrap();
                   extendr_api::Robj::from(
-                          get_submodule_metadata()
+                          get_dataframe_metadata()
                               .make_r_wrappers(use_symbols, package_name)
                               .unwrap(),
                       )
@@ -612,124 +833,57 @@
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_submodule_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe { extendr_api::register_call_methods(info, get_submodule_metadata()) };
+          pub extern "C" fn R_init_dataframe_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_dataframe_metadata()) };
           }
       }
-      mod optional_ndarray {
-          use extendr_api::prelude::*;
-          /// Calculate Euclidean distance matrix
-          /// Test case adopted from https://github.com/mikemahoney218/examplerust/blob/23d21b1ced4e24b7a7c00dd36290114dc1bbd113/src/rust/src/lib.rs#L5
-          /// @param a : Matrix of real values or `NULL`
-          /// @export
-          fn euclidean_dist(a: Nullable<ArrayView2<Rfloat>>) -> Nullable<Doubles> {
-              if let NotNull(a) = a {
-                  let nrow = a.nrows();
-                  let result = (0..(nrow - 1))
-                      .map(|x| {
-                          ((x + 1)..nrow)
-                              .map(move |y| {
-                                  let z = &a
-                                      .slice(
-                                          match x {
-                                              r => {
-                                                  match .. {
-                                                      r => {
-                                                          let in_dim = ::ndarray::SliceNextDim::next_in_dim(
-                                                              &r,
-                                                              ::ndarray::SliceNextDim::next_in_dim(
-                                                                  &r,
-                                                                  ::core::marker::PhantomData::<::ndarray::Ix0>,
-                                                              ),
-                                                          );
-                                                          let out_dim = ::ndarray::SliceNextDim::next_out_dim(
-                                                              &r,
-                                                              ::ndarray::SliceNextDim::next_out_dim(
-                                                                  &r,
-                                                                  ::core::marker::PhantomData::<::ndarray::Ix0>,
-                                                              ),
-                                                          );
-                                                          #[allow(unsafe_code)]
-                                                          unsafe {
-                                                              ::ndarray::SliceInfo::new_unchecked(
-                                                                  [
-                                                                      <::ndarray::SliceInfoElem as ::core::convert::From<
-                                                                          _,
-                                                                      >>::from(r),
-                                                                      <::ndarray::SliceInfoElem as ::core::convert::From<
-                                                                          _,
-                                                                      >>::from(r),
-                                                                  ],
-                                                                  in_dim,
-                                                                  out_dim,
-                                                              )
-                                                          }
-                                                      }
-                                                  }
-                                              }
-                                          },
-                                      )
-                                      - &a
-                                          .slice(
-                                              match y {
-                                                  r => {
-                                                      match .. {
-                                                          r => {
-                                                              let in_dim = ::ndarray::SliceNextDim::next_in_dim(
-                                                                  &r,
-                                                                  ::ndarray::SliceNextDim::next_in_dim(
-                                                                      &r,
-                                                                      ::core::marker::PhantomData::<::ndarray::Ix0>,
-                                                                  ),
-                                                              );
-                                                              let out_dim = ::ndarray::SliceNextDim::next_out_dim(
-                                                                  &r,
-                                                                  ::ndarray::SliceNextDim::next_out_dim(
-                                                                      &r,
-                                                                      ::core::marker::PhantomData::<::ndarray::Ix0>,
-                                                                  ),
-                                                              );
-                                                              #[allow(unsafe_code)]
-                                                              unsafe {
-                                                                  ::ndarray::SliceInfo::new_unchecked(
-                                                                      [
-                                                                          <::ndarray::SliceInfoElem as ::core::convert::From<
-                                                                              _,
-                                                                          >>::from(r),
-                                                                          <::ndarray::SliceInfoElem as ::core::convert::From<
-                                                                              _,
-                                                                          >>::from(r),
-                                                                      ],
-                                                                      in_dim,
-                                                                      out_dim,
-                                                                  )
-                                                              }
-                                                          }
-                                                      }
-                                                  }
-                                              },
-                                          );
-                                  (&z * &z).iter().sum::<Rfloat>().sqrt()
-                              })
-                      })
-                      .flatten()
-                      .collect();
-                  Nullable::NotNull(result)
-              } else {
-                  Nullable::Null
+      mod graphic_device {
+          use extendr_api::{graphics::*, prelude::*};
+          pub(crate) struct MyDevice<'a> {
+              pub(crate) welcome_message: &'a str,
+          }
+          impl<'a> DeviceDriver for MyDevice<'a> {
+              fn activate(&mut self, _dd: DevDesc) {
+                  let welcome_message = self.welcome_message;
+                  print_r_output({
+                      let res = ::alloc::fmt::format(
+                          format_args!("message from device: {0}", welcome_message),
+                      );
+                      res
+                  });
+                  print_r_output("\n");
+              }
+              fn close(&mut self, _dd: DevDesc) {
+                  print_r_output({
+                      let res = ::alloc::fmt::format(format_args!("good bye..."));
+                      res
+                  });
+                  print_r_output("\n");
               }
           }
+      }
+      mod memory_leaks {
+          use extendr_api::prelude::*;
+          fn leak_implicit_strings(x: Strings) -> String {
+              x.len().to_string()
+          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__euclidean_dist(a: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__leak_implicit_strings(
+              x: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _a_robj = extendr_api::robj::Robj::from_sexp(a);
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(euclidean_dist(_a_robj.try_into()?)))
+                      Ok(
+                          extendr_api::Robj::from(
+                              leak_implicit_strings(<Strings>::from_robj(&_x_robj)?),
+                          ),
+                      )
                   })
               };
               match wrap_result_state {
@@ -745,7 +899,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "euclidean_dist"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "leak_implicit_strings",
+                              ),
                           );
                           res
                       };
@@ -773,158 +930,143 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__euclidean_dist(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_implicit_strings(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "a",
-                          arg_type: "Nullable",
+                          name: "x",
+                          arg_type: "Strings",
                           default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Calculate Euclidean distance matrix\n Test case adopted from https://github.com/mikemahoney218/examplerust/blob/23d21b1ced4e24b7a7c00dd36290114dc1bbd113/src/rust/src/lib.rs#L5\n @param a : Matrix of real values or `NULL`\n @export",
-                      rust_name: "euclidean_dist",
-                      r_name: "euclidean_dist",
-                      mod_name: "euclidean_dist",
+                      doc: "",
+                      rust_name: "leak_implicit_strings",
+                      r_name: "leak_implicit_strings",
+                      mod_name: "leak_implicit_strings",
                       args: args,
-                      return_type: "Nullable",
-                      func_ptr: wrap__euclidean_dist as *const u8,
+                      return_type: "String",
+                      func_ptr: wrap__leak_implicit_strings as *const u8,
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_optional_ndarray_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__euclidean_dist(&mut functions);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_optional_ndarray_metadata",
-                      mod_name: "get_optional_ndarray_metadata",
-                      r_name: "get_optional_ndarray_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_optional_ndarray_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_optional_ndarray_wrappers",
-                      mod_name: "make_optional_ndarray_wrappers",
-                      r_name: "make_optional_ndarray_wrappers",
-                      args: <[_]>::into_vec(
-                          #[rustc_box]
-                          ::alloc::boxed::Box::new([
-                              extendr_api::metadata::Arg {
-                                  name: "use_symbols",
-                                  arg_type: "bool",
-                                  default: None,
-                              },
-                              extendr_api::metadata::Arg {
-                                  name: "package_name",
-                                  arg_type: "&str",
-                                  default: None,
-                              },
-                          ]),
-                      ),
-                      return_type: "String",
-                      func_ptr: wrap__make_optional_ndarray_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "optional_ndarray",
-                  functions,
-                  impls,
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_optional_ndarray_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_optional_ndarray_metadata()).get() }
+          fn leak_implicit_doubles(x: Doubles) -> String {
+              x.len().to_string()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_optional_ndarray_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
+          pub extern "C" fn wrap__leak_implicit_doubles(
+              x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
-              unsafe {
-                  use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_optional_ndarray_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      Ok(
+                          extendr_api::Robj::from(
+                              leak_implicit_doubles(<Doubles>::from_robj(&_x_robj)?),
+                          ),
                       )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_optional_ndarray_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe {
-                  extendr_api::register_call_methods(info, get_optional_ndarray_metadata())
+                  })
               };
-          }
-      }
-      mod graphic_device {
-          use extendr_api::{graphics::*, prelude::*};
-          pub(crate) struct MyDevice<'a> {
-              pub(crate) welcome_message: &'a str,
-          }
-          impl<'a> DeviceDriver for MyDevice<'a> {
-              fn activate(&mut self, _dd: DevDesc) {
-                  let welcome_message = self.welcome_message;
-                  print_r_output({
-                      let res = ::alloc::fmt::format(
-                          format_args!("message from device: {0}", welcome_message),
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "leak_implicit_doubles",
+                              ),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
                       );
-                      res
-                  });
-                  print_r_output("\n");
+                  }
               }
-              fn close(&mut self, _dd: DevDesc) {
-                  print_r_output({
-                      let res = ::alloc::fmt::format(format_args!("good bye..."));
-                      res
-                  });
-                  print_r_output("\n");
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
               }
           }
-      }
-      mod optional_either {
-          use extendr_api::prelude::*;
-          fn type_aware_sum(input: Either<Integers, Doubles>) -> Either<Rint, Rfloat> {
-              match input {
-                  Left(left) => Left(left.iter().sum()),
-                  Right(right) => Right(right.iter().sum()),
-              }
+          #[allow(non_snake_case)]
+          fn meta__leak_implicit_doubles(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "Doubles",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "leak_implicit_doubles",
+                      r_name: "leak_implicit_doubles",
+                      mod_name: "leak_implicit_doubles",
+                      args: args,
+                      return_type: "String",
+                      func_ptr: wrap__leak_implicit_doubles as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn leak_arg2_try_implicit_strings(_y: Doubles, x: Strings) -> String {
+              x.len().to_string()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__type_aware_sum(
-              input: extendr_api::SEXP,
+          pub extern "C" fn wrap__leak_arg2_try_implicit_strings(
+              _y: extendr_api::SEXP,
+              x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _input_robj = extendr_api::robj::Robj::from_sexp(input);
+                  let __y_robj = extendr_api::robj::Robj::from_sexp(_y);
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(type_aware_sum(_input_robj.try_into()?)))
+                      Ok(
+                          extendr_api::Robj::from(
+                              leak_arg2_try_implicit_strings(
+                                  __y_robj.try_into()?,
+                                  _x_robj.try_into()?,
+                              ),
+                          ),
+                      )
                   })
               };
               match wrap_result_state {
@@ -940,7 +1082,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "type_aware_sum"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "leak_arg2_try_implicit_strings",
+                              ),
                           );
                           res
                       };
@@ -968,13 +1113,20 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__type_aware_sum(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_arg2_try_implicit_strings(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "input",
-                          arg_type: "Either",
+                          name: "_y",
+                          arg_type: "Doubles",
+                          default: None,
+                      },
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "Strings",
                           default: None,
                       },
                   ]),
@@ -982,122 +1134,38 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "type_aware_sum",
-                      r_name: "type_aware_sum",
-                      mod_name: "type_aware_sum",
+                      rust_name: "leak_arg2_try_implicit_strings",
+                      r_name: "leak_arg2_try_implicit_strings",
+                      mod_name: "leak_arg2_try_implicit_strings",
                       args: args,
-                      return_type: "Either",
-                      func_ptr: wrap__type_aware_sum as *const u8,
+                      return_type: "String",
+                      func_ptr: wrap__leak_arg2_try_implicit_strings as *const u8,
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_optional_either_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__type_aware_sum(&mut functions);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_optional_either_metadata",
-                      mod_name: "get_optional_either_metadata",
-                      r_name: "get_optional_either_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_optional_either_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_optional_either_wrappers",
-                      mod_name: "make_optional_either_wrappers",
-                      r_name: "make_optional_either_wrappers",
-                      args: <[_]>::into_vec(
-                          #[rustc_box]
-                          ::alloc::boxed::Box::new([
-                              extendr_api::metadata::Arg {
-                                  name: "use_symbols",
-                                  arg_type: "bool",
-                                  default: None,
-                              },
-                              extendr_api::metadata::Arg {
-                                  name: "package_name",
-                                  arg_type: "&str",
-                                  default: None,
-                              },
-                          ]),
-                      ),
-                      return_type: "String",
-                      func_ptr: wrap__make_optional_either_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "optional_either",
-                  functions,
-                  impls,
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_optional_either_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_optional_either_metadata()).get() }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_optional_either_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              unsafe {
-                  use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_optional_either_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
-                      )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_optional_either_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe {
-                  extendr_api::register_call_methods(info, get_optional_either_metadata())
-              };
-          }
-      }
-      mod raw_identifiers {
-          use extendr_api::prelude::*;
-          /// Test raw identifiers (`r#`) in function arguments are parsed correctly.
-          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
-          /// @param type : i32 or `NULL`
-          /// @export
-          fn raw_identifier_in_fn_args(r#type: Nullable<i32>) -> Nullable<i32> {
-              r#type
+          fn leak_arg2_try_implicit_doubles(_y: Doubles, x: Doubles) -> String {
+              x.len().to_string()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__raw_identifier_in_fn_args(
-              r#type: extendr_api::SEXP,
+          pub extern "C" fn wrap__leak_arg2_try_implicit_doubles(
+              _y: extendr_api::SEXP,
+              x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _type_robj = extendr_api::robj::Robj::from_sexp(r#type);
+                  let __y_robj = extendr_api::robj::Robj::from_sexp(_y);
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              raw_identifier_in_fn_args(_type_robj.try_into()?),
+                              leak_arg2_try_implicit_doubles(
+                                  __y_robj.try_into()?,
+                                  _x_robj.try_into()?,
+                              ),
                           ),
                       )
                   })
@@ -1117,7 +1185,7 @@
                           let res = ::alloc::fmt::format(
                               format_args!(
                                   "user function panicked: {0}",
-                                  "raw_identifier_in_fn_args",
+                                  "leak_arg2_try_implicit_doubles",
                               ),
                           );
                           res
@@ -1146,45 +1214,57 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__raw_identifier_in_fn_args(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_arg2_try_implicit_doubles(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "r#type",
-                          arg_type: "Nullable",
-                          default: Some("NULL"),
+                          name: "_y",
+                          arg_type: "Doubles",
+                          default: None,
+                      },
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "Doubles",
+                          default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Test raw identifiers (`r#`) in function arguments are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @param type : i32 or `NULL`\n @export",
-                      rust_name: "raw_identifier_in_fn_args",
-                      r_name: "raw_identifier_in_fn_args",
-                      mod_name: "raw_identifier_in_fn_args",
+                      doc: "",
+                      rust_name: "leak_arg2_try_implicit_doubles",
+                      r_name: "leak_arg2_try_implicit_doubles",
+                      mod_name: "leak_arg2_try_implicit_doubles",
                       args: args,
-                      return_type: "Nullable",
-                      func_ptr: wrap__raw_identifier_in_fn_args as *const u8,
+                      return_type: "String",
+                      func_ptr: wrap__leak_arg2_try_implicit_doubles as *const u8,
                       hidden: false,
                   })
           }
-          /// Test raw identifiers (`r#`) as function names are parsed correctly.
-          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
-          /// @export
-          fn r#true() -> bool {
-              true
+          fn leak_unwrap_strings(x: Robj) -> String {
+              let x = x.as_string_vector().ok_or("ERROR").unwrap();
+              x.len().to_string()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__true() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__leak_unwrap_strings(
+              x: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(r#true()))
+                      Ok(
+                          extendr_api::Robj::from(
+                              leak_unwrap_strings(<Robj>::from_robj(&_x_robj)?),
+                          ),
+                      )
                   })
               };
               match wrap_result_state {
@@ -1200,7 +1280,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "r#true"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "leak_unwrap_strings",
+                              ),
                           );
                           res
                       };
@@ -1228,38 +1311,49 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__true(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = ::alloc::vec::Vec::new();
+          fn meta__leak_unwrap_strings(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "Robj",
+                          default: None,
+                      },
+                  ]),
+              );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Test raw identifiers (`r#`) as function names are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @export",
-                      rust_name: "r#true",
-                      r_name: "r#true",
-                      mod_name: "true",
+                      doc: "",
+                      rust_name: "leak_unwrap_strings",
+                      r_name: "leak_unwrap_strings",
+                      mod_name: "leak_unwrap_strings",
                       args: args,
-                      return_type: "bool",
-                      func_ptr: wrap__true as *const u8,
+                      return_type: "String",
+                      func_ptr: wrap__leak_unwrap_strings as *const u8,
                       hidden: false,
                   })
           }
-          /// Combine raw identifiers (`r#`) as a function name and in arguments are parsed correctly.
-          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
-          /// @param type : i32 or `NULL`
-          /// @export
-          fn r#false(r#type: bool) -> bool {
-              !r#type
+          fn leak_unwrap_doubles(x: Robj) -> String {
+              x.as_real_vector().ok_or("ERROR").unwrap().len().to_string()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__false(r#type: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__leak_unwrap_doubles(
+              x: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _type_robj = extendr_api::robj::Robj::from_sexp(r#type);
+                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(r#false(_type_robj.try_into()?)))
+                      Ok(
+                          extendr_api::Robj::from(
+                              leak_unwrap_doubles(<Robj>::from_robj(&_x_robj)?),
+                          ),
+                      )
                   })
               };
               match wrap_result_state {
@@ -1275,7 +1369,10 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "r#false"),
+                              format_args!(
+                                  "user function panicked: {0}",
+                                  "leak_unwrap_doubles",
+                              ),
                           );
                           res
                       };
@@ -1303,122 +1400,35 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__false(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_unwrap_doubles(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "r#type",
-                          arg_type: "bool",
+                          name: "x",
+                          arg_type: "Robj",
                           default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: " Combine raw identifiers (`r#`) as a function name and in arguments are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @param type : i32 or `NULL`\n @export",
-                      rust_name: "r#false",
-                      r_name: "r#false",
-                      mod_name: "false",
+                      doc: "",
+                      rust_name: "leak_unwrap_doubles",
+                      r_name: "leak_unwrap_doubles",
+                      mod_name: "leak_unwrap_doubles",
                       args: args,
-                      return_type: "bool",
-                      func_ptr: wrap__false as *const u8,
+                      return_type: "String",
+                      func_ptr: wrap__leak_unwrap_doubles as *const u8,
                       hidden: false,
                   })
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_raw_identifiers_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__raw_identifier_in_fn_args(&mut functions);
-              meta__true(&mut functions);
-              meta__false(&mut functions);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_raw_identifiers_metadata",
-                      mod_name: "get_raw_identifiers_metadata",
-                      r_name: "get_raw_identifiers_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_raw_identifiers_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_raw_identifiers_wrappers",
-                      mod_name: "make_raw_identifiers_wrappers",
-                      r_name: "make_raw_identifiers_wrappers",
-                      args: <[_]>::into_vec(
-                          #[rustc_box]
-                          ::alloc::boxed::Box::new([
-                              extendr_api::metadata::Arg {
-                                  name: "use_symbols",
-                                  arg_type: "bool",
-                                  default: None,
-                              },
-                              extendr_api::metadata::Arg {
-                                  name: "package_name",
-                                  arg_type: "&str",
-                                  default: None,
-                              },
-                          ]),
-                      ),
-                      return_type: "String",
-                      func_ptr: wrap__make_raw_identifiers_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "raw_identifiers",
-                  functions,
-                  impls,
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_raw_identifiers_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_raw_identifiers_metadata()).get() }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_raw_identifiers_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              unsafe {
-                  use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_raw_identifiers_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
-                      )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_raw_identifiers_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe {
-                  extendr_api::register_call_methods(info, get_raw_identifiers_metadata())
-              };
-          }
-      }
-      mod memory_leaks {
-          use extendr_api::prelude::*;
-          fn leak_implicit_strings(x: Strings) -> String {
-              x.len().to_string()
+          }
+          fn leak_positive_control(x: Robj) {
+              std::mem::forget(x);
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_implicit_strings(
+          pub extern "C" fn wrap__leak_positive_control(
               x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
@@ -1430,7 +1440,7 @@
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              leak_implicit_strings(<Strings>::from_robj(&_x_robj)?),
+                              leak_positive_control(<Robj>::from_robj(&_x_robj)?),
                           ),
                       )
                   })
@@ -1450,7 +1460,7 @@
                           let res = ::alloc::fmt::format(
                               format_args!(
                                   "user function panicked: {0}",
-                                  "leak_implicit_strings",
+                                  "leak_positive_control",
                               ),
                           );
                           res
@@ -1479,13 +1489,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_implicit_strings(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_positive_control(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "Strings",
+                          arg_type: "Robj",
                           default: None,
                       },
                   ]),
@@ -1493,21 +1503,21 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "leak_implicit_strings",
-                      r_name: "leak_implicit_strings",
-                      mod_name: "leak_implicit_strings",
+                      rust_name: "leak_positive_control",
+                      r_name: "leak_positive_control",
+                      mod_name: "leak_positive_control",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_implicit_strings as *const u8,
+                      return_type: "()",
+                      func_ptr: wrap__leak_positive_control as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_implicit_doubles(x: Doubles) -> String {
-              x.len().to_string()
+          fn leak_negative_control(x: Robj) {
+              drop(x)
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_implicit_doubles(
+          pub extern "C" fn wrap__leak_negative_control(
               x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
@@ -1519,7 +1529,7 @@
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              leak_implicit_doubles(<Doubles>::from_robj(&_x_robj)?),
+                              leak_negative_control(<Robj>::from_robj(&_x_robj)?),
                           ),
                       )
                   })
@@ -1539,7 +1549,7 @@
                           let res = ::alloc::fmt::format(
                               format_args!(
                                   "user function panicked: {0}",
-                                  "leak_implicit_doubles",
+                                  "leak_negative_control",
                               ),
                           );
                           res
@@ -1568,13 +1578,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_implicit_doubles(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__leak_negative_control(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "Doubles",
+                          arg_type: "Robj",
                           default: None,
                       },
                   ]),
@@ -1582,40 +1592,124 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "leak_implicit_doubles",
-                      r_name: "leak_implicit_doubles",
-                      mod_name: "leak_implicit_doubles",
+                      rust_name: "leak_negative_control",
+                      r_name: "leak_negative_control",
+                      mod_name: "leak_negative_control",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_implicit_doubles as *const u8,
+                      return_type: "()",
+                      func_ptr: wrap__leak_negative_control as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_arg2_try_implicit_strings(_y: Doubles, x: Strings) -> String {
-              x.len().to_string()
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_memory_leaks_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__leak_implicit_strings(&mut functions);
+              meta__leak_implicit_doubles(&mut functions);
+              meta__leak_arg2_try_implicit_strings(&mut functions);
+              meta__leak_arg2_try_implicit_doubles(&mut functions);
+              meta__leak_unwrap_strings(&mut functions);
+              meta__leak_unwrap_doubles(&mut functions);
+              meta__leak_positive_control(&mut functions);
+              meta__leak_negative_control(&mut functions);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_memory_leaks_metadata",
+                      mod_name: "get_memory_leaks_metadata",
+                      r_name: "get_memory_leaks_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_memory_leaks_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_memory_leaks_wrappers",
+                      mod_name: "make_memory_leaks_wrappers",
+                      r_name: "make_memory_leaks_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_memory_leaks_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "memory_leaks",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_memory_leaks_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_memory_leaks_metadata()).get() }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_arg2_try_implicit_strings(
-              _y: extendr_api::SEXP,
-              x: extendr_api::SEXP,
+          pub extern "C" fn wrap__make_memory_leaks_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_memory_leaks_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_memory_leaks_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_memory_leaks_metadata()) };
+          }
+      }
+      mod optional_either {
+          use extendr_api::prelude::*;
+          fn type_aware_sum(input: Either<Integers, Doubles>) -> Either<Rint, Rfloat> {
+              match input {
+                  Left(left) => Left(left.iter().sum()),
+                  Right(right) => Right(right.iter().sum()),
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__type_aware_sum(
+              input: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let __y_robj = extendr_api::robj::Robj::from_sexp(_y);
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                  let _input_robj = extendr_api::robj::Robj::from_sexp(input);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              leak_arg2_try_implicit_strings(
-                                  __y_robj.try_into()?,
-                                  _x_robj.try_into()?,
-                              ),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(type_aware_sum(_input_robj.try_into()?)))
                   })
               };
               match wrap_result_state {
@@ -1631,10 +1725,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!(
-                                  "user function panicked: {0}",
-                                  "leak_arg2_try_implicit_strings",
-                              ),
+                              format_args!("user function panicked: {0}", "type_aware_sum"),
                           );
                           res
                       };
@@ -1662,20 +1753,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_arg2_try_implicit_strings(
-              metadata: &mut Vec<extendr_api::metadata::Func>,
-          ) {
+          fn meta__type_aware_sum(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "_y",
-                          arg_type: "Doubles",
-                          default: None,
-                      },
-                      extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "Strings",
+                          name: "input",
+                          arg_type: "Either",
                           default: None,
                       },
                   ]),
@@ -1683,38 +1767,385 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "leak_arg2_try_implicit_strings",
-                      r_name: "leak_arg2_try_implicit_strings",
-                      mod_name: "leak_arg2_try_implicit_strings",
+                      rust_name: "type_aware_sum",
+                      r_name: "type_aware_sum",
+                      mod_name: "type_aware_sum",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_arg2_try_implicit_strings as *const u8,
+                      return_type: "Either",
+                      func_ptr: wrap__type_aware_sum as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_arg2_try_implicit_doubles(_y: Doubles, x: Doubles) -> String {
-              x.len().to_string()
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_optional_either_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__type_aware_sum(&mut functions);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_optional_either_metadata",
+                      mod_name: "get_optional_either_metadata",
+                      r_name: "get_optional_either_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_optional_either_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_optional_either_wrappers",
+                      mod_name: "make_optional_either_wrappers",
+                      r_name: "make_optional_either_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_optional_either_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "optional_either",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_optional_either_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_optional_either_metadata()).get() }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_arg2_try_implicit_doubles(
-              _y: extendr_api::SEXP,
-              x: extendr_api::SEXP,
+          pub extern "C" fn wrap__make_optional_either_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_optional_either_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_optional_either_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe {
+                  extendr_api::register_call_methods(info, get_optional_either_metadata())
+              };
+          }
+      }
+      mod optional_ndarray {
+          use extendr_api::prelude::*;
+          /// Calculate Euclidean distance matrix
+          /// Test case adopted from https://github.com/mikemahoney218/examplerust/blob/23d21b1ced4e24b7a7c00dd36290114dc1bbd113/src/rust/src/lib.rs#L5
+          /// @param a : Matrix of real values or `NULL`
+          /// @export
+          fn euclidean_dist(a: Nullable<ArrayView2<Rfloat>>) -> Nullable<Doubles> {
+              if let NotNull(a) = a {
+                  let nrow = a.nrows();
+                  let result = (0..(nrow - 1))
+                      .map(|x| {
+                          ((x + 1)..nrow)
+                              .map(move |y| {
+                                  let z = &a
+                                      .slice(
+                                          match x {
+                                              r => {
+                                                  match .. {
+                                                      r => {
+                                                          let in_dim = ::ndarray::SliceNextDim::next_in_dim(
+                                                              &r,
+                                                              ::ndarray::SliceNextDim::next_in_dim(
+                                                                  &r,
+                                                                  ::core::marker::PhantomData::<::ndarray::Ix0>,
+                                                              ),
+                                                          );
+                                                          let out_dim = ::ndarray::SliceNextDim::next_out_dim(
+                                                              &r,
+                                                              ::ndarray::SliceNextDim::next_out_dim(
+                                                                  &r,
+                                                                  ::core::marker::PhantomData::<::ndarray::Ix0>,
+                                                              ),
+                                                          );
+                                                          #[allow(unsafe_code)]
+                                                          unsafe {
+                                                              ::ndarray::SliceInfo::new_unchecked(
+                                                                  [
+                                                                      <::ndarray::SliceInfoElem as ::core::convert::From<
+                                                                          _,
+                                                                      >>::from(r),
+                                                                      <::ndarray::SliceInfoElem as ::core::convert::From<
+                                                                          _,
+                                                                      >>::from(r),
+                                                                  ],
+                                                                  in_dim,
+                                                                  out_dim,
+                                                              )
+                                                          }
+                                                      }
+                                                  }
+                                              }
+                                          },
+                                      )
+                                      - &a
+                                          .slice(
+                                              match y {
+                                                  r => {
+                                                      match .. {
+                                                          r => {
+                                                              let in_dim = ::ndarray::SliceNextDim::next_in_dim(
+                                                                  &r,
+                                                                  ::ndarray::SliceNextDim::next_in_dim(
+                                                                      &r,
+                                                                      ::core::marker::PhantomData::<::ndarray::Ix0>,
+                                                                  ),
+                                                              );
+                                                              let out_dim = ::ndarray::SliceNextDim::next_out_dim(
+                                                                  &r,
+                                                                  ::ndarray::SliceNextDim::next_out_dim(
+                                                                      &r,
+                                                                      ::core::marker::PhantomData::<::ndarray::Ix0>,
+                                                                  ),
+                                                              );
+                                                              #[allow(unsafe_code)]
+                                                              unsafe {
+                                                                  ::ndarray::SliceInfo::new_unchecked(
+                                                                      [
+                                                                          <::ndarray::SliceInfoElem as ::core::convert::From<
+                                                                              _,
+                                                                          >>::from(r),
+                                                                          <::ndarray::SliceInfoElem as ::core::convert::From<
+                                                                              _,
+                                                                          >>::from(r),
+                                                                      ],
+                                                                      in_dim,
+                                                                      out_dim,
+                                                                  )
+                                                              }
+                                                          }
+                                                      }
+                                                  }
+                                              },
+                                          );
+                                  (&z * &z).iter().sum::<Rfloat>().sqrt()
+                              })
+                      })
+                      .flatten()
+                      .collect();
+                  Nullable::NotNull(result)
+              } else {
+                  Nullable::Null
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__euclidean_dist(a: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let __y_robj = extendr_api::robj::Robj::from_sexp(_y);
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                  let _a_robj = extendr_api::robj::Robj::from_sexp(a);
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      Ok(extendr_api::Robj::from(euclidean_dist(_a_robj.try_into()?)))
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!("user function panicked: {0}", "euclidean_dist"),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__euclidean_dist(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "a",
+                          arg_type: "Nullable",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Calculate Euclidean distance matrix\n Test case adopted from https://github.com/mikemahoney218/examplerust/blob/23d21b1ced4e24b7a7c00dd36290114dc1bbd113/src/rust/src/lib.rs#L5\n @param a : Matrix of real values or `NULL`\n @export",
+                      rust_name: "euclidean_dist",
+                      r_name: "euclidean_dist",
+                      mod_name: "euclidean_dist",
+                      args: args,
+                      return_type: "Nullable",
+                      func_ptr: wrap__euclidean_dist as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_optional_ndarray_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__euclidean_dist(&mut functions);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_optional_ndarray_metadata",
+                      mod_name: "get_optional_ndarray_metadata",
+                      r_name: "get_optional_ndarray_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_optional_ndarray_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_optional_ndarray_wrappers",
+                      mod_name: "make_optional_ndarray_wrappers",
+                      r_name: "make_optional_ndarray_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_optional_ndarray_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "optional_ndarray",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_optional_ndarray_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_optional_ndarray_metadata()).get() }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__make_optional_ndarray_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_optional_ndarray_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_optional_ndarray_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe {
+                  extendr_api::register_call_methods(info, get_optional_ndarray_metadata())
+              };
+          }
+      }
+      mod raw_identifiers {
+          use extendr_api::prelude::*;
+          /// Test raw identifiers (`r#`) in function arguments are parsed correctly.
+          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
+          /// @param type : i32 or `NULL`
+          /// @export
+          fn raw_identifier_in_fn_args(r#type: Nullable<i32>) -> Nullable<i32> {
+              r#type
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__raw_identifier_in_fn_args(
+              r#type: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  let _type_robj = extendr_api::robj::Robj::from_sexp(r#type);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              leak_arg2_try_implicit_doubles(
-                                  __y_robj.try_into()?,
-                                  _x_robj.try_into()?,
-                              ),
+                              raw_identifier_in_fn_args(_type_robj.try_into()?),
                           ),
                       )
                   })
@@ -1734,7 +2165,7 @@
                           let res = ::alloc::fmt::format(
                               format_args!(
                                   "user function panicked: {0}",
-                                  "leak_arg2_try_implicit_doubles",
+                                  "raw_identifier_in_fn_args",
                               ),
                           );
                           res
@@ -1763,57 +2194,120 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_arg2_try_implicit_doubles(
-              metadata: &mut Vec<extendr_api::metadata::Func>,
-          ) {
+          fn meta__raw_identifier_in_fn_args(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "_y",
-                          arg_type: "Doubles",
-                          default: None,
-                      },
-                      extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "Doubles",
-                          default: None,
+                          name: "r#type",
+                          arg_type: "Nullable",
+                          default: Some("NULL"),
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "leak_arg2_try_implicit_doubles",
-                      r_name: "leak_arg2_try_implicit_doubles",
-                      mod_name: "leak_arg2_try_implicit_doubles",
+                      doc: " Test raw identifiers (`r#`) in function arguments are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @param type : i32 or `NULL`\n @export",
+                      rust_name: "raw_identifier_in_fn_args",
+                      r_name: "raw_identifier_in_fn_args",
+                      mod_name: "raw_identifier_in_fn_args",
+                      args: args,
+                      return_type: "Nullable",
+                      func_ptr: wrap__raw_identifier_in_fn_args as *const u8,
+                      hidden: false,
+                  })
+          }
+          /// Test raw identifiers (`r#`) as function names are parsed correctly.
+          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
+          /// @export
+          fn r#true() -> bool {
+              true
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__true() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      Ok(extendr_api::Robj::from(r#true()))
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!("user function panicked: {0}", "r#true"),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__true(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Test raw identifiers (`r#`) as function names are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @export",
+                      rust_name: "r#true",
+                      r_name: "r#true",
+                      mod_name: "true",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_arg2_try_implicit_doubles as *const u8,
+                      return_type: "bool",
+                      func_ptr: wrap__true as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_unwrap_strings(x: Robj) -> String {
-              let x = x.as_string_vector().ok_or("ERROR").unwrap();
-              x.len().to_string()
+          /// Combine raw identifiers (`r#`) as a function name and in arguments are parsed correctly.
+          /// See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.
+          /// @param type : i32 or `NULL`
+          /// @export
+          fn r#false(r#type: bool) -> bool {
+              !r#type
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_unwrap_strings(
-              x: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__false(r#type: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                  let _type_robj = extendr_api::robj::Robj::from_sexp(r#type);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              leak_unwrap_strings(<Robj>::from_robj(&_x_robj)?),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(r#false(_type_robj.try_into()?)))
                   })
               };
               match wrap_result_state {
@@ -1829,10 +2323,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!(
-                                  "user function panicked: {0}",
-                                  "leak_unwrap_strings",
-                              ),
+                              format_args!("user function panicked: {0}", "r#false"),
                           );
                           res
                       };
@@ -1860,49 +2351,131 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_unwrap_strings(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__false(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "Robj",
+                          name: "r#type",
+                          arg_type: "bool",
                           default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "leak_unwrap_strings",
-                      r_name: "leak_unwrap_strings",
-                      mod_name: "leak_unwrap_strings",
+                      doc: " Combine raw identifiers (`r#`) as a function name and in arguments are parsed correctly.\n See [Issue #582](https://github.com/extendr/extendr/issues/528) for details.\n @param type : i32 or `NULL`\n @export",
+                      rust_name: "r#false",
+                      r_name: "r#false",
+                      mod_name: "false",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_unwrap_strings as *const u8,
+                      return_type: "bool",
+                      func_ptr: wrap__false as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_unwrap_doubles(x: Robj) -> String {
-              x.as_real_vector().ok_or("ERROR").unwrap().len().to_string()
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_raw_identifiers_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__raw_identifier_in_fn_args(&mut functions);
+              meta__true(&mut functions);
+              meta__false(&mut functions);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_raw_identifiers_metadata",
+                      mod_name: "get_raw_identifiers_metadata",
+                      r_name: "get_raw_identifiers_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_raw_identifiers_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_raw_identifiers_wrappers",
+                      mod_name: "make_raw_identifiers_wrappers",
+                      r_name: "make_raw_identifiers_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_raw_identifiers_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "raw_identifiers",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_raw_identifiers_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_raw_identifiers_metadata()).get() }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_unwrap_doubles(
-              x: extendr_api::SEXP,
+          pub extern "C" fn wrap__make_raw_identifiers_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_raw_identifiers_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_raw_identifiers_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe {
+                  extendr_api::register_call_methods(info, get_raw_identifiers_metadata())
+              };
+          }
+      }
+      mod submodule {
+          use extendr_api::prelude::*;
+          /// Return string `"Hello world!"` to R.
+          /// @export
+          fn hello_submodule() -> &'static str {
+              "Hello World!"
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__hello_submodule() -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              leak_unwrap_doubles(<Robj>::from_robj(&_x_robj)?),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(hello_submodule()))
                   })
               };
               match wrap_result_state {
@@ -1918,10 +2491,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!(
-                                  "user function panicked: {0}",
-                                  "leak_unwrap_doubles",
-                              ),
+                              format_args!("user function panicked: {0}", "hello_submodule"),
                           );
                           res
                       };
@@ -1949,49 +2519,106 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_unwrap_doubles(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = <[_]>::into_vec(
-                  #[rustc_box]
-                  ::alloc::boxed::Box::new([
-                      extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "Robj",
-                          default: None,
-                      },
-                  ]),
-              );
+          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "leak_unwrap_doubles",
-                      r_name: "leak_unwrap_doubles",
-                      mod_name: "leak_unwrap_doubles",
+                      doc: " Return string `\"Hello world!\"` to R.\n @export",
+                      rust_name: "hello_submodule",
+                      r_name: "hello_submodule",
+                      mod_name: "hello_submodule",
                       args: args,
-                      return_type: "String",
-                      func_ptr: wrap__leak_unwrap_doubles as *const u8,
+                      return_type: "str",
+                      func_ptr: wrap__hello_submodule as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_positive_control(x: Robj) {
-              std::mem::forget(x);
+          struct MySubmoduleClass {
+              a: i32,
+          }
+          #[automatically_derived]
+          impl ::core::default::Default for MySubmoduleClass {
+              #[inline]
+              fn default() -> MySubmoduleClass {
+                  MySubmoduleClass {
+                      a: ::core::default::Default::default(),
+                  }
+              }
+          }
+          #[automatically_derived]
+          impl ::core::fmt::Debug for MySubmoduleClass {
+              #[inline]
+              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                  ::core::fmt::Formatter::debug_struct_field1_finish(
+                      f,
+                      "MySubmoduleClass",
+                      "a",
+                      &&self.a,
+                  )
+              }
+          }
+          #[automatically_derived]
+          impl ::core::clone::Clone for MySubmoduleClass {
+              #[inline]
+              fn clone(&self) -> MySubmoduleClass {
+                  let _: ::core::clone::AssertParamIsClone<i32>;
+                  *self
+              }
+          }
+          #[automatically_derived]
+          impl ::core::marker::Copy for MySubmoduleClass {}
+          /// Class for testing (exported)
+          /// @examples
+          /// x <- MySubmoduleClass$new()
+          /// x$a()
+          /// x$set_a(10)
+          /// x$a()
+          /// @export
+          impl MySubmoduleClass {
+              /// Method for making a new object.
+              fn new() -> Self {
+                  Self { a: 0 }
+              }
+              /// Method for setting stuff.
+              /// @param x a number
+              fn set_a(&mut self, x: i32) {
+                  self.a = x;
+              }
+              /// Method for getting stuff.
+              fn a(&self) -> i32 {
+                  self.a
+              }
+              /// Method for getting one's (by way of a copy) self.
+              fn me_owned(&self) -> Self {
+                  *self
+              }
+              /// Method for getting one's (ref) self.
+              fn me_ref(&self) -> &Self {
+                  self
+              }
+              /// Method for getting one's (ref mut) self.
+              fn me_mut(&mut self) -> &mut Self {
+                  self
+              }
+              /// Method for getting one's ref (explicit) self.
+              fn me_explicit_ref(&self) -> &MySubmoduleClass {
+                  self
+              }
+              /// Method for getting one's ref mut (explicit) self.
+              fn me_explicit_mut(&mut self) -> &mut MySubmoduleClass {
+                  self
+              }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_positive_control(
-              x: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__MySubmoduleClass__new() -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(
-                          extendr_api::Robj::from(
-                              leak_positive_control(<Robj>::from_robj(&_x_robj)?),
-                          ),
-                      )
+                      Ok(extendr_api::Robj::from(<MySubmoduleClass>::new()))
                   })
               };
               match wrap_result_state {
@@ -2007,10 +2634,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!(
-                                  "user function panicked: {0}",
-                                  "leak_positive_control",
-                              ),
+                              format_args!("user function panicked: {0}", "new"),
                           );
                           res
                       };
@@ -2038,35 +2662,24 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_positive_control(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = <[_]>::into_vec(
-                  #[rustc_box]
-                  ::alloc::boxed::Box::new([
-                      extendr_api::metadata::Arg {
-                          name: "x",
-                          arg_type: "Robj",
-                          default: None,
-                      },
-                  ]),
-              );
+          fn meta__MySubmoduleClass__new(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = ::alloc::vec::Vec::new();
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "leak_positive_control",
-                      r_name: "leak_positive_control",
-                      mod_name: "leak_positive_control",
+                      doc: " Method for making a new object.",
+                      rust_name: "new",
+                      r_name: "new",
+                      mod_name: "new",
                       args: args,
-                      return_type: "()",
-                      func_ptr: wrap__leak_positive_control as *const u8,
+                      return_type: "Self",
+                      func_ptr: wrap__MySubmoduleClass__new as *const u8,
                       hidden: false,
                   })
           }
-          fn leak_negative_control(x: Robj) {
-              drop(x)
-          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__leak_negative_control(
+          pub extern "C" fn wrap__MySubmoduleClass__set_a(
+              _self: extendr_api::SEXP,
               x: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
@@ -2074,11 +2687,15 @@
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   let _x_robj = extendr_api::robj::Robj::from_sexp(x);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              leak_negative_control(<Robj>::from_robj(&_x_robj)?),
+                              extendr_api::unwrap_or_throw(
+                                      <&mut MySubmoduleClass>::from_robj(&_self_robj),
+                                  )
+                                  .set_a(<i32>::from_robj(&_x_robj)?),
                           ),
                       )
                   })
@@ -2096,10 +2713,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!(
-                                  "user function panicked: {0}",
-                                  "leak_negative_control",
-                              ),
+                              format_args!("user function panicked: {0}", "set_a"),
                           );
                           res
                       };
@@ -2127,235 +2741,138 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__leak_negative_control(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__MySubmoduleClass__set_a(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "Robj",
+                          arg_type: "i32",
                           default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "leak_negative_control",
-                      r_name: "leak_negative_control",
-                      mod_name: "leak_negative_control",
+                      doc: " Method for setting stuff.\n @param x a number",
+                      rust_name: "set_a",
+                      r_name: "set_a",
+                      mod_name: "set_a",
                       args: args,
                       return_type: "()",
-                      func_ptr: wrap__leak_negative_control as *const u8,
+                      func_ptr: wrap__MySubmoduleClass__set_a as *const u8,
                       hidden: false,
                   })
           }
           #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_memory_leaks_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__leak_implicit_strings(&mut functions);
-              meta__leak_implicit_doubles(&mut functions);
-              meta__leak_arg2_try_implicit_strings(&mut functions);
-              meta__leak_arg2_try_implicit_doubles(&mut functions);
-              meta__leak_unwrap_strings(&mut functions);
-              meta__leak_unwrap_doubles(&mut functions);
-              meta__leak_positive_control(&mut functions);
-              meta__leak_negative_control(&mut functions);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_memory_leaks_metadata",
-                      mod_name: "get_memory_leaks_metadata",
-                      r_name: "get_memory_leaks_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_memory_leaks_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_memory_leaks_wrappers",
-                      mod_name: "make_memory_leaks_wrappers",
-                      r_name: "make_memory_leaks_wrappers",
-                      args: <[_]>::into_vec(
-                          #[rustc_box]
-                          ::alloc::boxed::Box::new([
-                              extendr_api::metadata::Arg {
-                                  name: "use_symbols",
-                                  arg_type: "bool",
-                                  default: None,
-                              },
-                              extendr_api::metadata::Arg {
-                                  name: "package_name",
-                                  arg_type: "&str",
-                                  default: None,
-                              },
-                          ]),
-                      ),
-                      return_type: "String",
-                      func_ptr: wrap__make_memory_leaks_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "memory_leaks",
-                  functions,
-                  impls,
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_memory_leaks_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_memory_leaks_metadata()).get() }
-          }
-          #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_memory_leaks_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
+          pub extern "C" fn wrap__MySubmoduleClass__a(
+              _self: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
-              unsafe {
-                  use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::from_robj(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_memory_leaks_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      Ok(
+                          extendr_api::Robj::from(
+                              extendr_api::unwrap_or_throw(
+                                      <&MySubmoduleClass>::from_robj(&_self_robj),
+                                  )
+                                  .a(),
+                          ),
                       )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_memory_leaks_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe { extendr_api::register_call_methods(info, get_memory_leaks_metadata()) };
-          }
-      }
-      mod altrep {
-          use extendr_api::prelude::*;
-          pub struct VecUsize(pub Vec<Option<usize>>);
-          #[automatically_derived]
-          impl ::core::fmt::Debug for VecUsize {
-              #[inline]
-              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                  ::core::fmt::Formatter::debug_tuple_field1_finish(f, "VecUsize", &&self.0)
-              }
-          }
-          #[automatically_derived]
-          impl ::core::clone::Clone for VecUsize {
-              #[inline]
-              fn clone(&self) -> VecUsize {
-                  VecUsize(::core::clone::Clone::clone(&self.0))
-              }
-          }
-          impl AltrepImpl for VecUsize {
-              fn length(&self) -> usize {
-                  self.0.len()
-              }
-          }
-          #[cfg(use_r_altlist)]
-          impl VecUsize {}
-          impl<'a> extendr_api::FromRobj<'a> for &VecUsize {
-              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-                  if robj.check_external_ptr_type::<VecUsize>() {
-                      #[allow(clippy::transmute_ptr_to_ref)]
-                      Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<VecUsize>()) })
-                  } else {
-                      Err("expected VecUsize")
-                  }
-              }
-          }
-          impl<'a> extendr_api::FromRobj<'a> for &mut VecUsize {
-              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
-                  if robj.check_external_ptr_type::<VecUsize>() {
-                      #[allow(clippy::transmute_ptr_to_ref)]
-                      Ok(unsafe { std::mem::transmute(robj.external_ptr_addr::<VecUsize>()) })
-                  } else {
-                      Err("expected VecUsize")
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
                   }
-              }
-          }
-          impl From<VecUsize> for Robj {
-              fn from(value: VecUsize) -> Self {
-                  unsafe {
-                      let ptr = Box::into_raw(Box::new(value));
-                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
-                      res.set_attrib(class_symbol(), "VecUsize").unwrap();
-                      res.register_c_finalizer(Some(__finalize__VecUsize));
-                      res
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
                   }
-              }
-          }
-          impl<'a> From<&'a VecUsize> for Robj {
-              fn from(value: &'a VecUsize) -> Self {
-                  unsafe {
-                      let ptr = Box::into_raw(Box::new(value));
-                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
-                      res.set_attrib(class_symbol(), "VecUsize").unwrap();
-                      res.register_c_finalizer(Some(__finalize__VecUsize));
-                      res
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!("user function panicked: {0}", "a"),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
                   }
               }
-          }
-          extern "C" fn __finalize__VecUsize(sexp: extendr_api::SEXP) {
-              unsafe {
-                  let robj = extendr_api::robj::Robj::from_sexp(sexp);
-                  if robj.check_external_ptr_type::<VecUsize>() {
-                      let ptr = robj.external_ptr_addr::<VecUsize>();
-                      drop(Box::from_raw(ptr));
-                  }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
               }
           }
           #[allow(non_snake_case)]
-          fn meta__VecUsize(impls: &mut Vec<extendr_api::metadata::Impl>) {
-              let mut methods = Vec::new();
-              impls
-                  .push(extendr_api::metadata::Impl {
-                      doc: "",
-                      name: "VecUsize",
-                      methods,
-                  });
-          }
-          #[cfg(use_r_altlist)]
-          impl AltListImpl for VecUsize {
-              fn elt(&self, index: usize) -> Robj {
-                  self.into_robj()
-              }
-          }
-          #[cfg(use_r_altlist)]
-          fn new_usize(robj: Integers) -> Altrep {
-              let x = robj
-                  .iter()
-                  .map(|x| match &x {
-                      _ if x.is_na() => None,
-                      _ if x.inner() < 0 => None,
-                      _ => Some(x.inner() as usize),
+          fn meta__MySubmoduleClass__a(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Method for getting stuff.",
+                      rust_name: "a",
+                      r_name: "a",
+                      mod_name: "a",
+                      args: args,
+                      return_type: "i32",
+                      func_ptr: wrap__MySubmoduleClass__a as *const u8,
+                      hidden: false,
                   })
-                  .collect();
-              let obj = VecUsize(x);
-              let class = Altrep::make_altlist_class::<VecUsize>("li", "mypkg");
-              Altrep::from_state_and_class(obj, class, false)
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__new_usize(robj: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__MySubmoduleClass__me_owned(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
-                  let _robj_robj = extendr_api::robj::Robj::from_sexp(robj);
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
                       Ok(
                           extendr_api::Robj::from(
-                              new_usize(<Integers>::from_robj(&_robj_robj)?),
+                              extendr_api::unwrap_or_throw(
+                                      <&MySubmoduleClass>::from_robj(&_self_robj),
+                                  )
+                                  .me_owned(),
                           ),
                       )
                   })
@@ -2373,7 +2890,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "new_usize"),
+                              format_args!("user function panicked: {0}", "me_owned"),
                           );
                           res
                       };
@@ -2401,82 +2918,214 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__new_usize(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__MySubmoduleClass__me_owned(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
               let mut args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
-                          name: "robj",
-                          arg_type: "Integers",
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
                           default: None,
                       },
                   ]),
               );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "new_usize",
-                      r_name: "new_usize",
-                      mod_name: "new_usize",
+                      doc: " Method for getting one's (by way of a copy) self.",
+                      rust_name: "me_owned",
+                      r_name: "me_owned",
+                      mod_name: "me_owned",
                       args: args,
-                      return_type: "Altrep",
-                      func_ptr: wrap__new_usize as *const u8,
+                      return_type: "Self",
+                      func_ptr: wrap__MySubmoduleClass__me_owned as *const u8,
                       hidden: false,
                   })
           }
-          struct StringInts {
-              len: usize,
-          }
-          #[automatically_derived]
-          impl ::core::fmt::Debug for StringInts {
-              #[inline]
-              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                  ::core::fmt::Formatter::debug_struct_field1_finish(
-                      f,
-                      "StringInts",
-                      "len",
-                      &&self.len,
-                  )
-              }
-          }
-          #[automatically_derived]
-          impl ::core::clone::Clone for StringInts {
-              #[inline]
-              fn clone(&self) -> StringInts {
-                  StringInts {
-                      len: ::core::clone::Clone::clone(&self.len),
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__MySubmoduleClass__me_ref(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      let _return_ref_to_self = extendr_api::unwrap_or_throw(
+                              <&MySubmoduleClass>::from_robj(&_self_robj),
+                          )
+                          .me_ref();
+                      Ok(_self_robj)
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!("user function panicked: {0}", "me_ref"),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
                   }
               }
-          }
-          impl AltrepImpl for StringInts {
-              fn length(&self) -> usize {
-                  self.len as usize
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
               }
           }
-          impl AltStringImpl for StringInts {
-              fn elt(&self, index: usize) -> Rstr {
-                  {
-                      let res = ::alloc::fmt::format(format_args!("{0}", index));
-                      res
+          #[allow(non_snake_case)]
+          fn meta__MySubmoduleClass__me_ref(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Method for getting one's (ref) self.",
+                      rust_name: "me_ref",
+                      r_name: "me_ref",
+                      mod_name: "me_ref",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__MySubmoduleClass__me_ref as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__MySubmoduleClass__me_mut(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                  std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
+                      let _return_ref_to_self = extendr_api::unwrap_or_throw(
+                              <&mut MySubmoduleClass>::from_robj(&_self_robj),
+                          )
+                          .me_mut();
+                      Ok(_self_robj)
+                  })
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = {
+                          let res = ::alloc::fmt::format(
+                              format_args!("user function panicked: {0}", "me_mut"),
+                          );
+                          res
+                      };
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
                   }
-                      .into()
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
               }
           }
-          fn tst_altstring() -> Altrep {
-              let mystate = StringInts { len: 10 };
-              let class = Altrep::make_altstring_class::<StringInts>("si", "mypkg");
-              Altrep::from_state_and_class(mystate, class, false)
-          }
+          #[allow(non_snake_case)]
+          fn meta__MySubmoduleClass__me_mut(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Method for getting one's (ref mut) self.",
+                      rust_name: "me_mut",
+                      r_name: "me_mut",
+                      mod_name: "me_mut",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__MySubmoduleClass__me_mut as *const u8,
+                      hidden: false,
+                  })
+          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__tst_altstring() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__MySubmoduleClass__me_explicit_ref(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(tst_altstring()))
+                      let _return_ref_to_self = extendr_api::unwrap_or_throw(
+                              <&MySubmoduleClass>::from_robj(&_self_robj),
+                          )
+                          .me_explicit_ref();
+                      Ok(_self_robj)
                   })
               };
               match wrap_result_state {
@@ -2492,7 +3141,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "tst_altstring"),
+                              format_args!("user function panicked: {0}", "me_explicit_ref"),
                           );
                           res
                       };
@@ -2520,90 +3169,48 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__tst_altstring(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = ::alloc::vec::Vec::new();
+          fn meta__MySubmoduleClass__me_explicit_ref(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
+                  ]),
+              );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "tst_altstring",
-                      r_name: "tst_altstring",
-                      mod_name: "tst_altstring",
+                      doc: " Method for getting one's ref (explicit) self.",
+                      rust_name: "me_explicit_ref",
+                      r_name: "me_explicit_ref",
+                      mod_name: "me_explicit_ref",
                       args: args,
-                      return_type: "Altrep",
-                      func_ptr: wrap__tst_altstring as *const u8,
+                      return_type: "MySubmoduleClass",
+                      func_ptr: wrap__MySubmoduleClass__me_explicit_ref as *const u8,
                       hidden: false,
                   })
           }
-          struct MyCompactIntRange {
-              start: i32,
-              len: i32,
-              step: i32,
-              missing_index: usize,
-          }
-          #[automatically_derived]
-          impl ::core::fmt::Debug for MyCompactIntRange {
-              #[inline]
-              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                  ::core::fmt::Formatter::debug_struct_field4_finish(
-                      f,
-                      "MyCompactIntRange",
-                      "start",
-                      &self.start,
-                      "len",
-                      &self.len,
-                      "step",
-                      &self.step,
-                      "missing_index",
-                      &&self.missing_index,
-                  )
-              }
-          }
-          #[automatically_derived]
-          impl ::core::clone::Clone for MyCompactIntRange {
-              #[inline]
-              fn clone(&self) -> MyCompactIntRange {
-                  MyCompactIntRange {
-                      start: ::core::clone::Clone::clone(&self.start),
-                      len: ::core::clone::Clone::clone(&self.len),
-                      step: ::core::clone::Clone::clone(&self.step),
-                      missing_index: ::core::clone::Clone::clone(&self.missing_index),
-                  }
-              }
-          }
-          impl AltrepImpl for MyCompactIntRange {
-              fn length(&self) -> usize {
-                  self.len as usize
-              }
-          }
-          impl AltIntegerImpl for MyCompactIntRange {
-              fn elt(&self, index: usize) -> Rint {
-                  if index == self.missing_index {
-                      Rint::na()
-                  } else {
-                      Rint::new(self.start + self.step * index as i32)
-                  }
-              }
-          }
-          fn tst_altinteger() -> Altrep {
-              let mystate = MyCompactIntRange {
-                  start: 0,
-                  len: 10,
-                  step: 1,
-                  missing_index: usize::MAX,
-              };
-              let class = Altrep::make_altinteger_class::<MyCompactIntRange>("cir", "mypkg");
-              Altrep::from_state_and_class(mystate, class.clone(), false)
-          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__tst_altinteger() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__MySubmoduleClass__me_explicit_mut(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<Robj, extendr_api::Error>,
                   Box<dyn std::any::Any + Send>,
               > = unsafe {
+                  let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
                   std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                      Ok(extendr_api::Robj::from(tst_altinteger()))
+                      let _return_ref_to_self = extendr_api::unwrap_or_throw(
+                              <&mut MySubmoduleClass>::from_robj(&_self_robj),
+                          )
+                          .me_explicit_mut();
+                      Ok(_self_robj)
                   })
               };
               match wrap_result_state {
@@ -2619,7 +3226,7 @@
                       drop(unwind_err);
                       let err_string = {
                           let res = ::alloc::fmt::format(
-                              format_args!("user function panicked: {0}", "tst_altinteger"),
+                              format_args!("user function panicked: {0}", "me_explicit_mut"),
                           );
                           res
                       };
@@ -2647,45 +3254,128 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__tst_altinteger(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let mut args = ::alloc::vec::Vec::new();
+          fn meta__MySubmoduleClass__me_explicit_mut(
+              metadata: &mut Vec<extendr_api::metadata::Func>,
+          ) {
+              let mut args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "MySubmoduleClass",
+                          default: None,
+                      },
+                  ]),
+              );
               metadata
                   .push(extendr_api::metadata::Func {
-                      doc: "",
-                      rust_name: "tst_altinteger",
-                      r_name: "tst_altinteger",
-                      mod_name: "tst_altinteger",
+                      doc: " Method for getting one's ref mut (explicit) self.",
+                      rust_name: "me_explicit_mut",
+                      r_name: "me_explicit_mut",
+                      mod_name: "me_explicit_mut",
                       args: args,
-                      return_type: "Altrep",
-                      func_ptr: wrap__tst_altinteger as *const u8,
+                      return_type: "MySubmoduleClass",
+                      func_ptr: wrap__MySubmoduleClass__me_explicit_mut as *const u8,
                       hidden: false,
                   })
           }
+          impl<'a> extendr_api::FromRobj<'a> for &MySubmoduleClass {
+              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
+                      #[allow(clippy::transmute_ptr_to_ref)]
+                      Ok(unsafe {
+                          std::mem::transmute(robj.external_ptr_addr::<MySubmoduleClass>())
+                      })
+                  } else {
+                      Err("expected MySubmoduleClass")
+                  }
+              }
+          }
+          impl<'a> extendr_api::FromRobj<'a> for &mut MySubmoduleClass {
+              fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
+                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
+                      #[allow(clippy::transmute_ptr_to_ref)]
+                      Ok(unsafe {
+                          std::mem::transmute(robj.external_ptr_addr::<MySubmoduleClass>())
+                      })
+                  } else {
+                      Err("expected MySubmoduleClass")
+                  }
+              }
+          }
+          impl From<MySubmoduleClass> for Robj {
+              fn from(value: MySubmoduleClass) -> Self {
+                  unsafe {
+                      let ptr = Box::into_raw(Box::new(value));
+                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
+                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
+                      res.register_c_finalizer(Some(__finalize__MySubmoduleClass));
+                      res
+                  }
+              }
+          }
+          impl<'a> From<&'a MySubmoduleClass> for Robj {
+              fn from(value: &'a MySubmoduleClass) -> Self {
+                  unsafe {
+                      let ptr = Box::into_raw(Box::new(value));
+                      let mut res = Robj::make_external_ptr(ptr, Robj::from(()));
+                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
+                      res.register_c_finalizer(Some(__finalize__MySubmoduleClass));
+                      res
+                  }
+              }
+          }
+          extern "C" fn __finalize__MySubmoduleClass(sexp: extendr_api::SEXP) {
+              unsafe {
+                  let robj = extendr_api::robj::Robj::from_sexp(sexp);
+                  if robj.check_external_ptr_type::<MySubmoduleClass>() {
+                      let ptr = robj.external_ptr_addr::<MySubmoduleClass>();
+                      drop(Box::from_raw(ptr));
+                  }
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__MySubmoduleClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
+              let mut methods = Vec::new();
+              meta__MySubmoduleClass__new(&mut methods);
+              meta__MySubmoduleClass__set_a(&mut methods);
+              meta__MySubmoduleClass__a(&mut methods);
+              meta__MySubmoduleClass__me_owned(&mut methods);
+              meta__MySubmoduleClass__me_ref(&mut methods);
+              meta__MySubmoduleClass__me_mut(&mut methods);
+              meta__MySubmoduleClass__me_explicit_ref(&mut methods);
+              meta__MySubmoduleClass__me_explicit_mut(&mut methods);
+              impls
+                  .push(extendr_api::metadata::Impl {
+                      doc: " Class for testing (exported)\n @examples\n x <- MySubmoduleClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
+                      name: "MySubmoduleClass",
+                      methods,
+                  });
+          }
           #[no_mangle]
           #[allow(non_snake_case)]
-          pub fn get_altrep_metadata() -> extendr_api::metadata::Metadata {
+          pub fn get_submodule_metadata() -> extendr_api::metadata::Metadata {
               let mut functions = Vec::new();
               let mut impls = Vec::new();
-              meta__new_usize(&mut functions);
-              meta__tst_altstring(&mut functions);
-              meta__tst_altinteger(&mut functions);
+              meta__hello_submodule(&mut functions);
+              meta__MySubmoduleClass(&mut impls);
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Metadata access function.",
-                      rust_name: "get_altrep_metadata",
-                      mod_name: "get_altrep_metadata",
-                      r_name: "get_altrep_metadata",
+                      rust_name: "get_submodule_metadata",
+                      mod_name: "get_submodule_metadata",
+                      r_name: "get_submodule_metadata",
                       args: Vec::new(),
                       return_type: "Metadata",
-                      func_ptr: wrap__get_altrep_metadata as *const u8,
+                      func_ptr: wrap__get_submodule_metadata as *const u8,
                       hidden: true,
                   });
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Wrapper generator.",
-                      rust_name: "make_altrep_wrappers",
-                      mod_name: "make_altrep_wrappers",
-                      r_name: "make_altrep_wrappers",
+                      rust_name: "make_submodule_wrappers",
+                      mod_name: "make_submodule_wrappers",
+                      r_name: "make_submodule_wrappers",
                       args: <[_]>::into_vec(
                           #[rustc_box]
                           ::alloc::boxed::Box::new([
@@ -2702,24 +3392,24 @@
                           ]),
                       ),
                       return_type: "String",
-                      func_ptr: wrap__make_altrep_wrappers as *const u8,
+                      func_ptr: wrap__make_submodule_wrappers as *const u8,
                       hidden: true,
                   });
               extendr_api::metadata::Metadata {
-                  name: "altrep",
+                  name: "submodule",
                   functions,
                   impls,
               }
           }
           #[no_mangle]
           #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_altrep_metadata() -> extendr_api::SEXP {
+          pub extern "C" fn wrap__get_submodule_metadata() -> extendr_api::SEXP {
               use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_altrep_metadata()).get() }
+              unsafe { extendr_api::Robj::from(get_submodule_metadata()).get() }
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_altrep_wrappers(
+          pub extern "C" fn wrap__make_submodule_wrappers(
               use_symbols_sexp: extendr_api::SEXP,
               package_name_sexp: extendr_api::SEXP,
           ) -> extendr_api::SEXP {
@@ -2731,7 +3421,7 @@
                   let robj = Robj::from_sexp(package_name_sexp);
                   let package_name: &str = <&str>::from_robj(&robj).unwrap();
                   extendr_api::Robj::from(
-                          get_altrep_metadata()
+                          get_submodule_metadata()
                               .make_r_wrappers(use_symbols, package_name)
                               .unwrap(),
                       )
@@ -2740,8 +3430,8 @@
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_altrep_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe { extendr_api::register_call_methods(info, get_altrep_metadata()) };
+          pub extern "C" fn R_init_submodule_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_submodule_metadata()) };
           }
       }
       fn hello_world() -> &'static str {
@@ -5144,11 +5834,11 @@
           > = unsafe {
               let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
               std::panic::catch_unwind(|| -> std::result::Result<Robj, extendr_api::Error> {
-                  Ok(
-                      extendr_api::Robj::from(
-                          extendr_api::unwrap_or_throw(<&MyClass>::from_robj(&_self_robj)).me(),
-                      ),
-                  )
+                  let _return_ref_to_self = extendr_api::unwrap_or_throw(
+                          <&MyClass>::from_robj(&_self_robj),
+                      )
+                      .me();
+                  Ok(_self_robj)
               })
           };
           match wrap_result_state {
@@ -6070,18 +6760,20 @@
           meta__MyClass(&mut impls);
           meta____MyClass(&mut impls);
           meta__MyClassUnexported(&mut impls);
-          functions.extend(submodule::get_submodule_metadata().functions);
-          functions.extend(optional_ndarray::get_optional_ndarray_metadata().functions);
+          functions.extend(altrep::get_altrep_metadata().functions);
+          functions.extend(dataframe::get_dataframe_metadata().functions);
+          functions.extend(memory_leaks::get_memory_leaks_metadata().functions);
           functions.extend(optional_either::get_optional_either_metadata().functions);
+          functions.extend(optional_ndarray::get_optional_ndarray_metadata().functions);
           functions.extend(raw_identifiers::get_raw_identifiers_metadata().functions);
-          functions.extend(memory_leaks::get_memory_leaks_metadata().functions);
-          functions.extend(altrep::get_altrep_metadata().functions);
-          impls.extend(submodule::get_submodule_metadata().impls);
-          impls.extend(optional_ndarray::get_optional_ndarray_metadata().impls);
+          functions.extend(submodule::get_submodule_metadata().functions);
+          impls.extend(altrep::get_altrep_metadata().impls);
+          impls.extend(dataframe::get_dataframe_metadata().impls);
+          impls.extend(memory_leaks::get_memory_leaks_metadata().impls);
           impls.extend(optional_either::get_optional_either_metadata().impls);
+          impls.extend(optional_ndarray::get_optional_ndarray_metadata().impls);
           impls.extend(raw_identifiers::get_raw_identifiers_metadata().impls);
-          impls.extend(memory_leaks::get_memory_leaks_metadata().impls);
-          impls.extend(altrep::get_altrep_metadata().impls);
+          impls.extend(submodule::get_submodule_metadata().impls);
           functions
               .push(extendr_api::metadata::Func {
                   doc: "Metadata access function.",
diff --git a/tests/extendrtests/tests/testthat/test-classes.R b/tests/extendrtests/tests/testthat/test-classes.R
index 72eb3b824..5714d7aec 100644
--- a/tests/extendrtests/tests/testthat/test-classes.R
+++ b/tests/extendrtests/tests/testthat/test-classes.R
@@ -11,7 +11,6 @@ test_that("Exported class works", {
 })
 
 test_that("Exported class self ptr works", {
-  skip("ExternalPtr issue https://github.com/extendr/extendr/issues/614")
   x <- MyClass$new()
   expect_equal(x$me(), x)
   expect_equal(x[["me"]](), x)
diff --git a/tests/extendrtests/tests/testthat/test-return-dataframe.R b/tests/extendrtests/tests/testthat/test-return-dataframe.R
new file mode 100644
index 000000000..9738f6c7f
--- /dev/null
+++ b/tests/extendrtests/tests/testthat/test-return-dataframe.R
@@ -0,0 +1,11 @@
+test_that("Dataframe<T> can be returned", {
+  expect_identical(
+    test_derive_into_dataframe(),
+    data.frame(x = 0:1, y = c("abc", "xyz"))
+  )
+
+  expect_identical(
+    test_into_robj_dataframe(),
+    data.frame(x = 0:1, y = c("abc", "xyz"))
+  )
+})
diff --git a/tests/extendrtests/tests/testthat/test-test-submodules.R b/tests/extendrtests/tests/testthat/test-test-submodules.R
index 863b05789..1b8111a79 100644
--- a/tests/extendrtests/tests/testthat/test-test-submodules.R
+++ b/tests/extendrtests/tests/testthat/test-test-submodules.R
@@ -6,4 +6,18 @@ test_that("Classes defined in submodules also works", {
   x <- MySubmoduleClass$new()
   x$set_a(10)
   expect_equal(x$a(), 10)
+
+  expect_equal(x$me_ref(), x)
+  expect_equal(x$me_mut(), x)
+  expect_equal(x$me_explicit_ref(), x)
+  expect_equal(x$me_explicit_mut(), x)
+
+  expect_identical(x$me_ref(), x)
+  expect_identical(x$me_mut(), x)
+  expect_identical(x$me_explicit_ref(), x)
+  expect_identical(x$me_explicit_mut(), x)
+  # this "copies"
+  expect_false(
+    isTRUE(identical(x$me_owned(), x))
+  )
 })
diff --git a/xtask/src/cli/devtools_test.rs b/xtask/src/cli/devtools_test.rs
new file mode 100644
index 000000000..7a2539c80
--- /dev/null
+++ b/xtask/src/cli/devtools_test.rs
@@ -0,0 +1,12 @@
+use clap::Args;
+
+#[derive(Args, Debug)]
+pub(crate) struct DevtoolsTestArg {
+    #[arg(
+        long,
+        short,
+        default_value = "false",
+        help = "Accept newly generated macro-snapshots"
+    )]
+    pub(crate) accept_snapshot: bool,
+}
diff --git a/xtask/src/commands/devtools_test.rs b/xtask/src/commands/devtools_test.rs
index c9ec66825..1ccdbb1c4 100644
--- a/xtask/src/commands/devtools_test.rs
+++ b/xtask/src/commands/devtools_test.rs
@@ -2,18 +2,28 @@ use std::error::Error;
 
 use xshell::{cmd, Shell};
 
-use crate::extendrtests::with_absolute_path::{swap_extendr_api_path, R_FOLDER_PATH};
+use crate::{
+    cli::devtools_test::DevtoolsTestArg,
+    extendrtests::with_absolute_path::{swap_extendr_api_path, R_FOLDER_PATH},
+};
 
-pub(crate) fn run(shell: &Shell) -> Result<(), Box<dyn Error>> {
+pub(crate) fn run(shell: &Shell, args: DevtoolsTestArg) -> Result<(), Box<dyn Error>> {
     let _document_handle = swap_extendr_api_path(shell)?;
 
-    run_tests(shell)?;
+    run_tests(shell, args)?;
 
     Ok(())
 }
 
-fn run_tests(shell: &Shell) -> Result<(), Box<dyn Error>> {
+fn run_tests(shell: &Shell, args: DevtoolsTestArg) -> Result<(), Box<dyn Error>> {
     let _r_path = shell.push_dir(R_FOLDER_PATH);
+    if args.accept_snapshot {
+        cmd!(
+            shell,
+            "Rscript -e testthat::snapshot_accept(\"macro-snapshot\")"
+        )
+        .run()?;
+    }
     cmd!(shell, "Rscript -e devtools::test()").run()?;
 
     Ok(())
diff --git a/xtask/src/extendrtests/with_absolute_path.rs b/xtask/src/extendrtests/with_absolute_path.rs
index 141ae0b04..706dc8c8a 100644
--- a/xtask/src/extendrtests/with_absolute_path.rs
+++ b/xtask/src/extendrtests/with_absolute_path.rs
@@ -1,7 +1,7 @@
 use std::error::Error;
 use std::path::Path;
 
-use toml_edit::{Document, InlineTable, Value};
+use toml_edit::{DocumentMut, InlineTable, Value};
 use xshell::Shell;
 
 use crate::extendrtests::path_helper::RCompatiblePath;
@@ -12,12 +12,12 @@ const RUST_FOLDER_PATH: &str = "tests/extendrtests/src/rust";
 const CARGO_TOML: &str = "Cargo.toml";
 
 #[derive(Debug)]
-pub(crate) struct DocumentHandle<'a> {
+pub(crate) struct DocumentMutHandle<'a> {
     document: Vec<u8>,
     shell: &'a Shell,
 }
 
-impl<'a> Drop for DocumentHandle<'a> {
+impl<'a> Drop for DocumentMutHandle<'a> {
     fn drop(&mut self) {
         let _rust_folder = self.shell.push_dir(RUST_FOLDER_PATH);
         self.shell
@@ -26,13 +26,14 @@ impl<'a> Drop for DocumentHandle<'a> {
     }
 }
 
-pub(crate) fn swap_extendr_api_path(shell: &Shell) -> Result<DocumentHandle, Box<dyn Error>> {
+pub(crate) fn swap_extendr_api_path(shell: &Shell) -> Result<DocumentMutHandle, Box<dyn Error>> {
     let current_path = shell.current_dir();
     let _rust_folder = shell.push_dir(RUST_FOLDER_PATH);
 
     let original_cargo_toml_bytes = read_file_with_line_ending(shell, CARGO_TOML)?;
 
-    let original_cargo_toml: Document = std::str::from_utf8(&original_cargo_toml_bytes)?.parse()?;
+    let original_cargo_toml: DocumentMut =
+        std::str::from_utf8(&original_cargo_toml_bytes)?.parse()?;
 
     let mut cargo_toml = original_cargo_toml.clone();
 
@@ -41,24 +42,46 @@ pub(crate) fn swap_extendr_api_path(shell: &Shell) -> Result<DocumentHandle, Box
 
     let mut replacement = InlineTable::new();
 
-    let item = Value::from(get_replacement_path(&current_path));
+    let item = Value::from(get_replacement_path_extendr_api(&current_path));
     replacement.entry("path").or_insert(item);
     *extendr_api_entry = Value::InlineTable(replacement);
 
+    #[allow(non_snake_case)]
+    let libR_sys_entry = get_libR_sys_entry(&mut cargo_toml);
+    #[allow(non_snake_case)]
+    if let Some(libR_sys_entry) = libR_sys_entry {
+        let mut replacement = InlineTable::new();
+        if let Some(replacement_path) = get_replacement_path_libR_sys(&current_path) {
+            let item = Value::from(replacement_path);
+            replacement.entry("path").or_insert(item);
+            *libR_sys_entry = Value::InlineTable(replacement);
+        }
+    }
+
+    // save altered paths
     shell.write_file(CARGO_TOML, cargo_toml.to_string())?;
-    Ok(DocumentHandle {
+    Ok(DocumentMutHandle {
         document: original_cargo_toml_bytes,
         shell,
     })
 }
 
-fn get_replacement_path(path: &Path) -> String {
+fn get_replacement_path_extendr_api(path: &Path) -> String {
     let path = path.adjust_for_r();
 
     format!("{path}/extendr-api")
 }
 
-fn get_extendr_api_entry(document: &mut Document) -> Option<&mut Value> {
+#[allow(non_snake_case)]
+fn get_replacement_path_libR_sys(path: &Path) -> Option<String> {
+    let path = path.adjust_for_r();
+    #[allow(non_snake_case)]
+    let libR_sys_path = format!("{path}/libR-sys");
+    let valid_path = std::path::Path::new(&libR_sys_path);
+    matches!(valid_path.try_exists(), Ok(true)).then_some(libR_sys_path)
+}
+
+fn get_extendr_api_entry(document: &mut DocumentMut) -> Option<&mut Value> {
     document
         .get_mut("patch")?
         .get_mut("crates-io")?
@@ -66,6 +89,15 @@ fn get_extendr_api_entry(document: &mut Document) -> Option<&mut Value> {
         .as_value_mut()
 }
 
+#[allow(non_snake_case)]
+fn get_libR_sys_entry(document: &mut DocumentMut) -> Option<&mut Value> {
+    document
+        .get_mut("patch")?
+        .get_mut("crates-io")?
+        .get_mut("libR-sys")?
+        .as_value_mut()
+}
+
 fn read_file_with_line_ending<P: AsRef<Path>>(
     shell: &Shell,
     path: P,