Repository: extendr/extendr
Pull request #883 (2025-02-25 19:10:53)

chore: avoid r-devel check
This PR :

- makes `cargo build` quiet in extendrtests so that CI for r-devel can finally pass. 
- adds `tools/msrv.R` to report rustc and cargo versions for r-devel
- addresses 0x1 error in 0 length vectors in r-devel


CGMossa: I must say, while I think these changes are good, they also reflect a potential adjustments needed on rextendr
The templates there doesn't have quiet as an arg for the build script. Or maybe they do.

It's never enough, but I think this will do. I will approve soon 
JosiahParry: The `--quiet` flag is used in lieu of using vendored dependencies here. The CRAN check for vendoring for Rust crates literally is a `grepl("Downloading crate", ...)` or something like that.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 475133585..1f59498d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Added
 
+- Type aliases `RMatrix4D` and `RMatrix5D` have been added <https://github.com/extendr/extendr/pull/875>
 - `ExternalPtr<T>` and `&ExternalPtr<T>` can now be used as function arguments with appropriate type checking <https://github.com/extendr/extendr/pull/853>
 - `TryFrom<Robj>` is now implemented for `&ExternalPtr<T>` and `&mut ExternalPtr<T>` <https://github.com/extendr/extendr/pull/833>
 - `Option<T>` can now be returned from `#[extendr]` annotated functions for extendr_api types. `None` is translated into `NULL` value in R [[#802]](https://github.com/extendr/extendr/pull/802)
@@ -15,6 +16,7 @@
 
 ### Changed
 
+- Breaking change: Adding `#[extendr]` above impl blocks no longer provides a default implementation for `impl From<T> for Robj` and `impl TryFrom<Robj> for T`. Instead, users may annotate their custom structs and enums with `#[extendr]` to get these default implementations. Note that a user can still derive their own implementations for these traits, and in this case should forego annotating their type with `#[extendr]`. With this change, users may use `#[extendr]` on multiple `impl <T>` blocks so long as they are contained within their own modules. <https://github.com/extendr/extendr/pull/882>
 - Enhancement: string comparisons are now implemented for `Rstr` using R's string intern mechanism. Making string comparisons _much_ faster. [[#845]](https://github.com/extendr/extendr/pull/845)
 - Breaking change: `RMatrix::get_rownames` and `RMatrix::get_colnames` now both
 return `Option<Strings>` instead of opaque `Robj`.
@@ -22,6 +24,8 @@ return `Option<Strings>` instead of opaque `Robj`.
 
 ### Fixed
 
+- Addresses change in R-Devel which turn [accessing 0 length vectors intto a segfault](https://github.com/wch/r-source/commit/d4dff56ff9d11ef96d67afccf8d9e8790d250664) <https://github.com/extendr/extendr/pull/883>
+
 ### Deprecated
 
 - Removed `from_sexp_ref()` from the public API. `from_sexp_ref()` is an unsafe method and is for internal use only. [#855]](https://github.com/extendr/extendr/pull/855)
diff --git a/README.md b/README.md
index 106dd2826..aab613cfa 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@ declaration and the methods in the `impl`.
 ```rust
 use extendr_api::prelude::*;
 
+#[extendr]
 struct Person {
     pub name: String,
 }
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index c3fef3c07..603732988 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -65,6 +65,8 @@ pub enum Error {
     ExpectedVector(Robj),
     ExpectedMatrix(Robj),
     ExpectedMatrix3D(Robj),
+    ExpectedMatrix4D(Robj),
+    ExpectedMatrix5D(Robj),
     ExpectedNumeric(Robj),
     ExpectedAltrep(Robj),
     ExpectedDataframe(Robj),
@@ -144,6 +146,8 @@ impl std::fmt::Display for Error {
             Error::ExpectedVector(robj) => write!(f, "Expected Vector, got {:?}", robj.rtype()),
             Error::ExpectedMatrix(robj) => write!(f, "Expected Matrix, got {:?}", robj.rtype()),
             Error::ExpectedMatrix3D(robj) => write!(f, "Expected Matrix3D, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix4D(robj) => write!(f, "Expected Matrix4D, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix5D(robj) => write!(f, "Expected Matrix5D, got {:?}", robj.rtype()),
             Error::ExpectedNumeric(robj) => write!(f, "Expected Numeric, got {:?}", robj.rtype()),
             Error::ExpectedAltrep(robj) => write!(f, "Expected Altrep, got {:?}", robj.rtype()),
             Error::ExpectedDataframe(robj) => {
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 2bfa2d3ff..5aba4b0c5 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -21,7 +21,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn fred;    
+//!    fn fred;
 //! }
 //!
 //! ```
@@ -41,26 +41,26 @@
 //!     // An R object with a single string "hello"
 //!     let character = r!("hello");
 //!     let character = r!(["hello", "goodbye"]);
-//!    
+//!
 //!     // An R integer object with a single number 1L.
 //!     // Note that in Rust, 1 is an integer and 1.0 is a real.
 //!     let integer = r!(1);
-//!    
+//!
 //!     // An R real object with a single number 1.
 //!     // Note that in R, 1 is a real and 1L is an integer.
 //!     let real = r!(1.0);
-//!    
+//!
 //!     // An R real vector.
 //!     let real_vector = r!([1.0, 2.0]);
 //!     let real_vector = &[1.0, 2.0].iter().collect_robj();
 //!     let real_vector = r!(vec![1.0, 2.0]);
-//!    
+//!
 //!     // An R function object.
 //!     let function = R!("function(x, y) { x + y }")?;
-//!    
+//!
 //!     // A named list using the list! macro.
 //!     let list = list!(a = 1, b = 2);
-//!    
+//!
 //!     // An unnamed list (of R objects) using the List wrapper.
 //!     let list = r!(List::from_values(vec![1, 2, 3]));
 //!     let list = r!(List::from_values(vec!["a", "b", "c"]));
@@ -82,11 +82,11 @@
 //!     // 1 ..= 100 is the same as 1:100
 //!     let res = r!(1 ..= 100);
 //!     assert_eq!(res, R!("1:100")?);
-//!    
+//!
 //!     // Rust arrays are zero-indexed so it is more common to use 0 .. 100.
 //!     let res = r!(0 .. 100);
 //!     assert_eq!(res.len(), 100);
-//!    
+//!
 //!     // Using map is a super fast way to generate vectors.
 //!     let iter = (0..3).map(|i| format!("fred{}", i));
 //!     let character = iter.collect_robj();
@@ -115,13 +115,13 @@
 //! use extendr_api::prelude::*;
 //! test! {
 //!     let vals = r!([1.0, 2.0, 3.0]);
-//!    
+//!
 //!     // one-based indexing [[i]], returns an object.
 //!     assert_eq!(vals.index(1)?, r!(1.0));
-//!    
+//!
 //!     // one-based slicing [x], returns an object.
 //!     assert_eq!(vals.slice(1..=2)?, r!([1.0, 2.0]));
-//!    
+//!
 //!     // $ operator, returns an object
 //!     let list = list!(a = 1.0, b = "xyz");
 //!     assert_eq!(list.dollar("a")?, r!(1.0));
@@ -191,7 +191,7 @@
 //! test! {
 //!     // robj is an "Owned" object that controls the memory allocated.
 //!     let robj = r!([1, 2, 3]);
-//!    
+//!
 //!     // Here slice is a "borrowed" reference to the bytes in robj.
 //!     // and cannot live longer than robj.
 //!     let slice = robj.as_integer_slice().ok_or("expected slice")?;
@@ -215,7 +215,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn things;    
+//!    fn things;
 //! }
 //!
 //!
@@ -253,7 +253,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn oups;    
+//!    fn oups;
 //! }
 //!
 //! ```
@@ -279,7 +279,7 @@
 //!
 //! # handling example for result_condition
 //! oups_handled <- function(a) {
-//!   val_or_err <- oups(1)  
+//!   val_or_err <- oups(1)
 //!   if (inherits(val_or_err, "extendr_error")) stop(val_or_err)
 //!   val_or_err
 //! }
@@ -760,6 +760,7 @@ mod tests {
         x
     }
 
+    #[extendr]
     struct Person {
         pub name: String,
     }
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index 4cbd41343..887860b45 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -297,27 +297,36 @@ fn write_method_wrapper(
 /// Generate a wrapper for an implementation block.
 fn write_impl_wrapper(
     w: &mut Vec<u8>,
-    imp: &Impl,
+    name: &str,
+    impls: &[Impl],
     package_name: &str,
     use_symbols: bool,
 ) -> std::io::Result<()> {
-    let exported = imp.doc.contains("@export");
-
-    write_doc(w, imp.doc)?;
+    let mut exported = false;
+    {
+        for imp in impls.iter().filter(|imp| imp.name == name) {
+            if !exported {
+                exported = imp.doc.contains("@export");
+            }
+            write_doc(w, imp.doc)?;
+        }
+    }
 
-    let imp_name_fixed = sanitize_identifier(imp.name);
+    let imp_name_fixed = sanitize_identifier(name);
 
     // Using fixed name because it is exposed to R
     writeln!(w, "{} <- new.env(parent = emptyenv())\n", imp_name_fixed)?;
 
-    for func in &imp.methods {
-        // write_doc(& mut w, func.doc)?;
-        // `imp.name` is passed as is and sanitized within the function
-        write_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
+    for imp in impls.iter().filter(|imp| imp.name == name) {
+        for func in &imp.methods {
+            // write_doc(& mut w, func.doc)?;
+            // `imp.name` is passed as is and sanitized within the function
+            write_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
+        }
     }
 
     if exported {
-        writeln!(w, "#' @rdname {}", imp.name)?;
+        writeln!(w, "#' @rdname {}", name)?;
         writeln!(w, "#' @usage NULL")?;
     }
 
@@ -330,10 +339,10 @@ fn write_impl_wrapper(
     // LHS with dollar operator is wrapped in ``, so pass name as is,
     // but in the body `imp_name_fixed` is called as valid R function,
     // so we pass preprocessed value
-    writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", imp.name, imp_name_fixed)?;
+    writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", name, imp_name_fixed)?;
 
     writeln!(w, "#' @export")?;
-    writeln!(w, "`[[.{}` <- `$.{}`\n", imp.name, imp.name)?;
+    writeln!(w, "`[[.{}` <- `$.{}`\n", name, name)?;
 
     Ok(())
 }
@@ -369,9 +378,20 @@ impl Metadata {
             write_function_wrapper(&mut w, func, package_name, use_symbols)?;
         }
 
-        for imp in &self.impls {
-            write_impl_wrapper(&mut w, imp, package_name, use_symbols)?;
+        for name in self.impl_names() {
+            write_impl_wrapper(&mut w, name, &self.impls, package_name, use_symbols)?;
         }
+
         unsafe { Ok(String::from_utf8_unchecked(w)) }
     }
+
+    fn impl_names<'a>(&'a self) -> Vec<&'a str> {
+        let mut vec: Vec<&str> = vec![];
+        for impls in &self.impls {
+            if !vec.contains(&impls.name) {
+                vec.push(&impls.name)
+            }
+        }
+        vec
+    }
 }
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index a56ff63b6..80cf72fd1 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -38,7 +38,7 @@ pub use crate::{
 
 pub use super::wrapper::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
-    AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D,
+    AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D, RMatrix4D, RMatrix5D,
 };
 
 #[cfg(use_r_altlist)]
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index d26bbcbf2..5b1389b13 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -777,6 +777,11 @@ macro_rules! make_typed_slice {
                 match self.sexptype() {
                     $( $sexp )|* => {
                         unsafe {
+                            // if the vector is empty return an empty slice
+                            if self.is_empty() {
+                                return Some(&[])
+                            }
+                            // otherwise get the slice
                             let ptr = $fn(self.get()) as *const $type;
                             Some(std::slice::from_raw_parts(ptr, self.len()))
                         }
@@ -789,8 +794,13 @@ macro_rules! make_typed_slice {
                 match self.sexptype() {
                     $( $sexp )|* => {
                         unsafe {
+                            if self.is_empty() {
+                                return Some(&mut []);
+                            }
                             let ptr = $fn(self.get_mut()) as *mut $type;
+
                             Some(std::slice::from_raw_parts_mut(ptr, self.len()))
+
                         }
                     }
                     _ => None
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
index 0f83e523b..cb5333fd4 100644
--- a/extendr-api/src/wrapper/matrix.rs
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -72,6 +72,8 @@ impl<T, D> RArray<T, D> {
 pub type RColumn<T> = RArray<T, [usize; 1]>;
 pub type RMatrix<T> = RArray<T, [usize; 2]>;
 pub type RMatrix3D<T> = RArray<T, [usize; 3]>;
+pub type RMatrix4D<T> = RArray<T, [usize; 4]>;
+pub type RMatrix5D<T> = RArray<T, [usize; 5]>;
 
 impl<T> RMatrix<T>
 where
@@ -385,6 +387,60 @@ where
     }
 }
 
+impl<T> TryFrom<&Robj> for RMatrix4D<T>
+where
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(robj: &Robj) -> Result<Self> {
+        if let Some(_slice) = robj.as_typed_slice() {
+            if let Some(dim) = robj.dim() {
+                if dim.len() != 4 {
+                    Err(Error::ExpectedMatrix4D(robj.clone()))
+                } else {
+                    let dim: Vec<_> = dim.iter().map(|d| d.inner() as usize).collect();
+                    Ok(RArray::from_parts(
+                        robj.clone(),
+                        [dim[0], dim[1], dim[2], dim[3]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix4D(robj.clone()))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj.clone()))
+        }
+    }
+}
+
+impl<T> TryFrom<&Robj> for RMatrix5D<T>
+where
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(robj: &Robj) -> Result<Self> {
+        if let Some(_slice) = robj.as_typed_slice() {
+            if let Some(dim) = robj.dim() {
+                if dim.len() != 5 {
+                    Err(Error::ExpectedMatrix5D(robj.clone()))
+                } else {
+                    let dim: Vec<_> = dim.iter().map(|d| d.inner() as usize).collect();
+                    Ok(RArray::from_parts(
+                        robj.clone(),
+                        [dim[0], dim[1], dim[2], dim[3], dim[4]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix5D(robj.clone()))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj.clone()))
+        }
+    }
+}
+
 macro_rules! impl_try_from_robj_ref {
     ($($type : tt)*) => {
         $(
@@ -432,6 +488,8 @@ impl_try_from_robj_ref!(
     RMatrix
     RColumn
     RMatrix3D
+    RMatrix4D
+    RMatrix5D
 );
 
 impl<T, D> From<RArray<T, D>> for Robj {
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 7dc478dba..c7d66415a 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -48,7 +48,7 @@ pub use integers::Integers;
 pub use lang::Language;
 pub use list::{FromList, List, ListIter};
 pub use logicals::Logicals;
-pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D};
+pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D, RMatrix4D, RMatrix5D};
 pub use nullable::Nullable;
 pub use pairlist::{Pairlist, PairlistIter};
 pub use primitive::Primitive;
diff --git a/extendr-macros/src/extendr_conversion.rs b/extendr-macros/src/extendr_conversion.rs
new file mode 100644
index 000000000..c4682b039
--- /dev/null
+++ b/extendr-macros/src/extendr_conversion.rs
@@ -0,0 +1,132 @@
+use crate::{extendr_options::ExtendrOptions, wrappers};
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::Attribute;
+use syn::Generics;
+use syn::Ident;
+use syn::Item;
+
+struct TypeFields {
+    ident: Ident,
+    generics: Generics,
+    attrs: Vec<Attribute>,
+}
+
+fn pull_fields(item: &Item) -> syn::Result<TypeFields> {
+    let fields = match item {
+        Item::Struct(str) => TypeFields {
+            ident: str.ident.clone(),
+            generics: str.generics.clone(),
+            attrs: str.attrs.clone(),
+        },
+        Item::Enum(str) => TypeFields {
+            ident: str.ident.clone(),
+            generics: str.generics.clone(),
+            attrs: str.attrs.clone(),
+        },
+        _ => {
+            return Err(syn::Error::new_spanned(
+                item,
+                "#[extendr] conversions can only be built for `struct` and `enum`",
+            ))
+        }
+    };
+    Ok(fields)
+}
+
+pub(crate) fn extendr_type_conversion(item: Item, opts: &ExtendrOptions) -> TokenStream {
+    match do_extendr_type_conversion(item, opts) {
+        Ok(result) => result,
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+
+fn do_extendr_type_conversion(item: Item, _opts: &ExtendrOptions) -> syn::Result<TokenStream> {
+    let TypeFields {
+        ident: self_ty,
+        generics,
+        attrs,
+    } = pull_fields(&item)?;
+    if generics.const_params().count() != 0 {
+        return Err(syn::Error::new_spanned(
+            item,
+            "const params not allowed in #[extendr] impl",
+        ));
+    }
+    let mut self_ty_name = self_ty.to_string();
+    for gen in generics.type_params() {
+        self_ty_name.push('_');
+        self_ty_name.push_str(gen.ident.to_string().as_str());
+    }
+    // residual code syntax from extendr_impl.rs
+    // let mut _prefix = format!("{}__", self_ty_name);
+
+    // To do: Should documenting the struct be moved to R?
+    // At the moment, only documentattion above the impl
+    // block makes it to R.
+    let _doc_string = wrappers::get_doc_string(&attrs);
+
+    let conversion_impls = quote! {
+        // Output conversion function for this type.
+
+        impl TryFrom<Robj> for &#self_ty {
+            type Error = extendr_api::Error;
+
+            fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                Self::try_from(&robj)
+            }
+        }
+
+        impl TryFrom<Robj> for &mut #self_ty {
+            type Error = extendr_api::Error;
+
+            fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                Self::try_from(&mut robj)
+            }
+        }
+
+        // Output conversion function for this type.
+        impl TryFrom<&Robj> for &#self_ty {
+            type Error = extendr_api::Error;
+            fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let external_ptr: &ExternalPtr<#self_ty> = robj.try_into()?;
+                    external_ptr.try_addr()
+                }
+            }
+        }
+
+        // Input conversion function for a mutable reference to this type.
+        impl TryFrom<&mut Robj> for &mut #self_ty {
+            type Error = extendr_api::Error;
+            fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let external_ptr: &mut ExternalPtr<#self_ty> = robj.try_into()?;
+                    external_ptr.try_addr_mut()
+                }
+            }
+        }
+    };
+
+    let output = TokenStream::from(quote! {
+        #item
+
+        #conversion_impls
+
+        // Output conversion function for this type.
+        impl From<#self_ty> for Robj {
+            fn from(value: #self_ty) -> Self {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let mut res: ExternalPtr<#self_ty> = ExternalPtr::new(value);
+                    res.set_attrib(class_symbol(), #self_ty_name).unwrap();
+                    res.into()
+                }
+            }
+        }
+
+    });
+    Ok(output)
+}
diff --git a/extendr-macros/src/extendr_impl.rs b/extendr-macros/src/extendr_impl.rs
index 8b84035f1..472c74f3a 100644
--- a/extendr-macros/src/extendr_impl.rs
+++ b/extendr-macros/src/extendr_impl.rs
@@ -58,7 +58,7 @@ use crate::wrappers;
 ///         // return self
 ///         self
 ///     }
-///     
+///
 ///     // Convert the struct into a data.frame
 ///     fn into_df(&self) -> Robj {
 ///         let df = self.0.clone().into_dataframe();
@@ -174,50 +174,6 @@ pub(crate) fn extendr_impl(
 
     let meta_name = format_ident!("{}{}", wrappers::META_PREFIX, self_ty_name);
 
-    let conversion_impls = quote! {
-        // Output conversion function for this type.
-
-        impl TryFrom<Robj> for &#self_ty {
-            type Error = extendr_api::Error;
-
-            fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                Self::try_from(&robj)
-            }
-        }
-
-        impl TryFrom<Robj> for &mut #self_ty {
-            type Error = extendr_api::Error;
-
-            fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                Self::try_from(&mut robj)
-            }
-        }
-
-        // Output conversion function for this type.
-        impl TryFrom<&Robj> for &#self_ty {
-            type Error = extendr_api::Error;
-            fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let external_ptr: &ExternalPtr<#self_ty> = robj.try_into()?;
-                    external_ptr.try_addr()
-                }
-            }
-        }
-
-        // Input conversion function for a mutable reference to this type.
-        impl TryFrom<&mut Robj> for &mut #self_ty {
-            type Error = extendr_api::Error;
-            fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let external_ptr: &mut ExternalPtr<#self_ty> = robj.try_into()?;
-                    external_ptr.try_addr_mut()
-                }
-            }
-        }
-    };
-
     let expanded = TokenStream::from(quote! {
         // The impl itself copied from the source.
         #item_impl
@@ -225,20 +181,6 @@ pub(crate) fn extendr_impl(
         // Function wrappers
         #( #wrappers )*
 
-        #conversion_impls
-
-        // Output conversion function for this type.
-        impl From<#self_ty> for Robj {
-            fn from(value: #self_ty) -> Self {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let mut res: ExternalPtr<#self_ty> = ExternalPtr::new(value);
-                    res.set_attrib(class_symbol(), #self_ty_name).unwrap();
-                    res.into()
-                }
-            }
-        }
-
         #[allow(non_snake_case)]
         fn #meta_name(impls: &mut Vec<extendr_api::metadata::Impl>) {
             let mut methods = Vec::new();
diff --git a/extendr-macros/src/lib.rs b/extendr-macros/src/lib.rs
index 8815d54ee..bec14776e 100644
--- a/extendr-macros/src/lib.rs
+++ b/extendr-macros/src/lib.rs
@@ -58,6 +58,7 @@
 mod R;
 mod call;
 mod dataframe;
+mod extendr_conversion;
 mod extendr_function;
 mod extendr_impl;
 mod extendr_module;
@@ -91,6 +92,8 @@ pub fn extendr(attr: TokenStream, item: TokenStream) -> TokenStream {
     parse_macro_input!(attr with extendr_opts_parser);
 
     match parse_macro_input!(item as Item) {
+        Item::Struct(str) => extendr_conversion::extendr_type_conversion(Item::Struct(str), &opts),
+        Item::Enum(enm) => extendr_conversion::extendr_type_conversion(Item::Enum(enm), &opts),
         Item::Fn(func) => extendr_function::extendr_function(func, &opts),
         Item::Impl(item_impl) => match extendr_impl::extendr_impl(item_impl, &opts) {
             Ok(result) => result,


Test patch:
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index 1d269530d..e799f3a69 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -244,6 +244,7 @@ fn test_altlist() {
     use extendr_api::AltListImpl;
     with_r(|| {
         #[derive(Debug, Clone)]
+        #[extendr]
         pub struct VecUsize(pub Vec<Option<usize>>);
 
         // need to make the VecUsize object `.into_robj()`-able
diff --git a/tests/extendrtests/.Rbuildignore b/tests/extendrtests/.Rbuildignore
index 7615186f7..1a2b7bf43 100644
--- a/tests/extendrtests/.Rbuildignore
+++ b/tests/extendrtests/.Rbuildignore
@@ -2,3 +2,5 @@
 ^\.Rproj\.user$
 ^LICENSE\.md$
 ^notebooks$
+^src/\.cargo$
+^src/rust/vendor$
diff --git a/tests/extendrtests/.gitignore b/tests/extendrtests/.gitignore
index b87f1551c..fd54ed4ef 100644
--- a/tests/extendrtests/.gitignore
+++ b/tests/extendrtests/.gitignore
@@ -40,3 +40,4 @@ vignettes/*.pdf
 
 # check dir
 check/
+src/rust/vendor
diff --git a/tests/extendrtests/DESCRIPTION b/tests/extendrtests/DESCRIPTION
index 083e15b2f..98c0d2485 100644
--- a/tests/extendrtests/DESCRIPTION
+++ b/tests/extendrtests/DESCRIPTION
@@ -11,7 +11,7 @@ Description:
   Provides tests for extendr functionality used from
   within an R package.
 License: MIT + file LICENSE
-SystemRequirements: Rust
+SystemRequirements: Cargo (Rust's package manager), rustc
 Encoding: UTF-8
 Roxygen: list(markdown = TRUE)
 RoxygenNote: 7.3.2
diff --git a/tests/extendrtests/NAMESPACE b/tests/extendrtests/NAMESPACE
index cbb2628e9..ea2deee4c 100644
--- a/tests/extendrtests/NAMESPACE
+++ b/tests/extendrtests/NAMESPACE
@@ -1,11 +1,13 @@
 # Generated by roxygen2: do not edit by hand
 
 S3method("$","__MyClass")
+S3method("$",Animal)
 S3method("$",MyClass)
 S3method("$",MyClassUnexported)
 S3method("$",MySubmoduleClass)
 S3method("$",Wrapper)
 S3method("[[","__MyClass")
+S3method("[[",Animal)
 S3method("[[",MyClass)
 S3method("[[",MyClassUnexported)
 S3method("[[",MySubmoduleClass)
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index f668e13ec..b2c0dc8e1 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -181,6 +181,12 @@ fetch_rownames <- function(x) .Call(wrap__fetch_rownames, x)
 
 change_dimnames <- function(x) .Call(wrap__change_dimnames, x)
 
+matrix_3d_return <- function(x) .Call(wrap__matrix_3d_return, x)
+
+matrix_4d_return <- function(x) .Call(wrap__matrix_4d_return, x)
+
+matrix_5d_return <- function(x) .Call(wrap__matrix_5d_return, x)
+
 to_unique_rstr <- function(r_char_vec) .Call(wrap__to_unique_rstr, r_char_vec)
 
 to_unique_str <- function(r_char_vec) .Call(wrap__to_unique_str, r_char_vec)
@@ -293,6 +299,8 @@ Wrapper$max_ref_offset <- function(other, `_offset`) .Call(wrap__Wrapper__max_re
 
 Wrapper$max_ref2 <- function(other) .Call(wrap__Wrapper__max_ref2, self, other)
 
+Wrapper$a_10 <- function() .Call(wrap__Wrapper__a_10, self)
+
 #' @rdname Wrapper
 #' @usage NULL
 #' @export
@@ -301,5 +309,19 @@ Wrapper$max_ref2 <- function(other) .Call(wrap__Wrapper__max_ref2, self, other)
 #' @export
 `[[.Wrapper` <- `$.Wrapper`
 
+Animal <- new.env(parent = emptyenv())
+
+Animal$new_dog <- function() .Call(wrap__Animal__new_dog)
+
+Animal$new_cat <- function() .Call(wrap__Animal__new_cat)
+
+Animal$speak <- function() .Call(wrap__Animal__speak, self)
+
+#' @export
+`$.Animal` <- function (self, name) { func <- Animal[[name]]; environment(func) <- environment(); func }
+
+#' @export
+`[[.Animal` <- `$.Animal`
+
 
 # nolint end
diff --git a/tests/extendrtests/configure b/tests/extendrtests/configure
new file mode 100755
index 000000000..6d0de9037
--- /dev/null
+++ b/tests/extendrtests/configure
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+: "${R_HOME=`R RHOME`}"
+"${R_HOME}/bin/Rscript" tools/msrv.R 
+
diff --git a/tests/extendrtests/configure.win b/tests/extendrtests/configure.win
new file mode 100644
index 000000000..0a5f76afe
--- /dev/null
+++ b/tests/extendrtests/configure.win
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/msrv.R
+
diff --git a/tests/extendrtests/man/Wrapper.Rd b/tests/extendrtests/man/Wrapper.Rd
index d207c744a..21d98cc71 100644
--- a/tests/extendrtests/man/Wrapper.Rd
+++ b/tests/extendrtests/man/Wrapper.Rd
@@ -6,7 +6,7 @@
 \alias{$.Wrapper}
 \title{Class for testing (exported)}
 \format{
-An object of class \code{environment} of length 11.
+An object of class \code{environment} of length 12.
 }
 \usage{
 Wrapper
diff --git a/tests/extendrtests/src/Makevars b/tests/extendrtests/src/Makevars
index 15d20edef..a996ee0a5 100644
--- a/tests/extendrtests/src/Makevars
+++ b/tests/extendrtests/src/Makevars
@@ -8,7 +8,7 @@ all: C_clean
 $(SHLIB): $(STATLIB)
 
 $(STATLIB):
-	cargo build --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) --color=auto
+	cargo build --quiet --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) --color=auto
 
 C_clean:
 	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)
diff --git a/tests/extendrtests/src/Makevars.ucrt b/tests/extendrtests/src/Makevars.ucrt
deleted file mode 100644
index 17b153e3d..000000000
--- a/tests/extendrtests/src/Makevars.ucrt
+++ /dev/null
@@ -1,5 +0,0 @@
-# Rtools42 doesn't have the linker in the location that cargo expects, so we
-# need to overwrite it via configuration.
-CARGO_LINKER = x86_64-w64-mingw32.static.posix-gcc.exe
-
-include Makevars.win
diff --git a/tests/extendrtests/src/Makevars.win b/tests/extendrtests/src/Makevars.win
index 88e191a43..e18b6c4d3 100644
--- a/tests/extendrtests/src/Makevars.win
+++ b/tests/extendrtests/src/Makevars.win
@@ -24,7 +24,7 @@ $(STATLIB):
 	# CARGO_LINKER is provided in Makevars.ucrt for R >= 4.2
 	export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER="$(CARGO_LINKER)" && \
 		export LIBRARY_PATH="$${LIBRARY_PATH};$(CURDIR)/$(TARGET_DIR)/libgcc_mock" && \
-		cargo build --target=$(TARGET) --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) --color=always
+		cargo build --quiet --target=$(TARGET) --lib --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) --color=always
 
 C_clean:
 	rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)
diff --git a/tests/extendrtests/src/extendrtests-win.def b/tests/extendrtests/src/extendrtests-win.def
new file mode 100644
index 000000000..46ec12cc7
--- /dev/null
+++ b/tests/extendrtests/src/extendrtests-win.def
@@ -0,0 +1,2 @@
+EXPORTS
+R_init_extendrtests
diff --git a/tests/extendrtests/src/rust/src/altrep.rs b/tests/extendrtests/src/rust/src/altrep.rs
index f71b4e165..4e75fe606 100644
--- a/tests/extendrtests/src/rust/src/altrep.rs
+++ b/tests/extendrtests/src/rust/src/altrep.rs
@@ -2,6 +2,7 @@ use extendr_api::prelude::*;
 
 // struct contains an inner vector of Option<usize>
 #[derive(Debug, Clone)]
+#[extendr]
 pub struct VecUsize(pub Vec<Option<usize>>);
 
 impl AltrepImpl for VecUsize {
diff --git a/tests/extendrtests/src/rust/src/externalptr.rs b/tests/extendrtests/src/rust/src/externalptr.rs
index 00f4069e4..26dca38f3 100644
--- a/tests/extendrtests/src/rust/src/externalptr.rs
+++ b/tests/extendrtests/src/rust/src/externalptr.rs
@@ -2,6 +2,7 @@ use extendr_api::prelude::*;
 
 // Class for testing
 #[derive(Default, Debug, Clone, Copy)]
+#[extendr]
 struct Wrapper {
     a: i32,
 }
@@ -107,11 +108,49 @@ fn sum_integer_externalptr(x: ExternalPtr<Integers>) -> Rint {
     x.into_iter().sum()
 }
 
+mod submod {
+    use super::*;
+
+    #[extendr]
+    impl Wrapper {
+        pub fn a_10(&self) -> i32 {
+            self.a + 10
+        }
+    }
+    extendr_module! {
+      mod submod;
+      impl Wrapper;
+    }
+}
+
+#[extendr]
+enum Animal {
+    Cat,
+    Dog,
+}
+
+#[extendr]
+impl Animal {
+    pub fn new_dog() -> Self {
+        Animal::Dog
+    }
+    pub fn new_cat() -> Self {
+        Animal::Cat
+    }
+    pub fn speak(&self) -> Strings {
+        match self {
+            Animal::Dog => Strings::from("woof"),
+            Animal::Cat => Strings::from("meow"),
+        }
+    }
+}
+
 // Macro to generate exports
 extendr_module! {
     mod externalptr;
     impl Wrapper;
-
+    use submod;
     fn create_numeric_externalptr;
     fn sum_integer_externalptr;
+    impl Animal;
 }
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 882089ed7..2ed7054ef 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -203,6 +203,7 @@ fn add_5_if_not_null(x: Nullable<Rint>) -> Nullable<Rint> {
 
 // Class for testing
 #[derive(Default, Debug)]
+#[extendr]
 struct MyClass {
     a: i32,
 }
@@ -251,6 +252,7 @@ impl MyClass {
 
 // Class for testing special names
 #[derive(Default, Debug)]
+#[extendr]
 struct __MyClass {}
 
 // Class for testing special names
@@ -267,6 +269,7 @@ impl __MyClass {
 
 // Class for testing (unexported)
 #[derive(Default, Debug)]
+#[extendr]
 struct MyClassUnexported {
     a: i32,
 }
diff --git a/tests/extendrtests/src/rust/src/matrix.rs b/tests/extendrtests/src/rust/src/matrix.rs
index 425fdadfa..16ce5319a 100644
--- a/tests/extendrtests/src/rust/src/matrix.rs
+++ b/tests/extendrtests/src/rust/src/matrix.rs
@@ -22,10 +22,28 @@ fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
     x.to_owned()
 }
 
+#[extendr]
+fn matrix_3d_return(x: RMatrix3D<f64>) -> RMatrix3D<f64> {
+    x
+}
+
+#[extendr]
+fn matrix_4d_return(x: RMatrix4D<f64>) -> RMatrix4D<f64> {
+    x
+}
+
+#[extendr]
+fn matrix_5d_return(x: RMatrix5D<f64>) -> RMatrix5D<f64> {
+    x
+}
+
 extendr_module! {
     mod matrix;
     fn fetch_dimnames;
     fn fetch_colnames;
     fn fetch_rownames;
     fn change_dimnames;
+    fn matrix_3d_return;
+    fn matrix_4d_return;
+    fn matrix_5d_return;
 }
diff --git a/tests/extendrtests/src/rust/src/submodule.rs b/tests/extendrtests/src/rust/src/submodule.rs
index 22e468e99..8e5fad10f 100644
--- a/tests/extendrtests/src/rust/src/submodule.rs
+++ b/tests/extendrtests/src/rust/src/submodule.rs
@@ -9,6 +9,7 @@ fn hello_submodule() -> &'static str {
 
 // Class for testing
 #[derive(Default, Debug, Clone, Copy)]
+#[extendr]
 struct MySubmoduleClass {
     a: i32,
 }
diff --git a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
index b062e6147..97d867fd4 100644
--- a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
+++ b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
@@ -12,27 +12,6 @@
       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 TryFrom<Robj> for &VecUsize {
               type Error = extendr_api::Error;
               fn try_from(robj: Robj) -> extendr_api::Result<Self> {
@@ -75,6 +54,27 @@
                   }
               }
           }
+          #[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 {}
           #[allow(non_snake_case)]
           fn meta__VecUsize(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -1211,6 +1211,48 @@
           struct Wrapper {
               a: i32,
           }
+          impl TryFrom<Robj> for &Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<Wrapper> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<Wrapper> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<Wrapper> for Robj {
+              fn from(value: Wrapper) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<Wrapper> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "Wrapper").unwrap();
+                      res.into()
+                  }
+              }
+          }
           #[automatically_derived]
           impl ::core::default::Default for Wrapper {
               #[inline]
@@ -2421,48 +2463,6 @@
                       hidden: false,
                   })
           }
-          impl TryFrom<Robj> for &Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&robj)
-              }
-          }
-          impl TryFrom<Robj> for &mut Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&mut robj)
-              }
-          }
-          impl TryFrom<&Robj> for &Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &ExternalPtr<Wrapper> = robj.try_into()?;
-                      external_ptr.try_addr()
-                  }
-              }
-          }
-          impl TryFrom<&mut Robj> for &mut Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &mut ExternalPtr<Wrapper> = robj.try_into()?;
-                      external_ptr.try_addr_mut()
-                  }
-              }
-          }
-          impl From<Wrapper> for Robj {
-              fn from(value: Wrapper) -> Self {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let mut res: ExternalPtr<Wrapper> = ExternalPtr::new(value);
-                      res.set_attrib(class_symbol(), "Wrapper").unwrap();
-                      res.into()
-                  }
-              }
-          }
           #[allow(non_snake_case)]
           fn meta__Wrapper(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -2755,126 +2755,793 @@
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_externalptr_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__create_numeric_externalptr(&mut functions);
-              meta__sum_integer_externalptr(&mut functions);
-              meta__Wrapper(&mut impls);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_externalptr_metadata",
-                      mod_name: "get_externalptr_metadata",
-                      r_name: "get_externalptr_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_externalptr_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_externalptr_wrappers",
-                      mod_name: "make_externalptr_wrappers",
-                      r_name: "make_externalptr_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_externalptr_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "externalptr",
-                  functions,
-                  impls,
+          mod submod {
+              use super::*;
+              impl Wrapper {
+                  pub fn a_10(&self) -> i32 {
+                      self.a + 10
+                  }
               }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_externalptr_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_externalptr_metadata()).get() }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_externalptr_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              unsafe {
+              #[no_mangle]
+              #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+              pub extern "C" fn wrap__Wrapper__a_10(
+                  _self: extendr_api::SEXP,
+              ) -> extendr_api::SEXP {
                   use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::try_from(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::try_from(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_externalptr_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
+                  let wrap_result_state: std::result::Result<
+                      std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                      Box<dyn std::any::Any + Send>,
+                  > = unsafe {
+                      std::panic::catch_unwind(
+                          std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                              extendr_api::Robj,
+                              extendr_api::Error,
+                          > {
+                              let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                              Ok(
+                                  extendr_api::Robj::from(
+                                      extendr_api::unwrap_or_throw_error(
+                                              <&Wrapper>::try_from(&_self_robj),
+                                          )
+                                          .a_10(),
+                                  ),
+                              )
+                          }),
                       )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_externalptr_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe { extendr_api::register_call_methods(info, get_externalptr_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(
-                      ::alloc::__export::must_use({
-                          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 = ::alloc::__export::must_use({
+                              let res = ::alloc::fmt::format(
+                                  format_args!("User function panicked: {0}", "a_10"),
+                              );
+                              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");
+                      }
+                  }
+                  {
+                      ::core::panicking::panic_fmt(
+                          format_args!(
+                              "internal error: entered unreachable code: {0}",
+                              format_args!("internal extendr error, this should never happen."),
+                          ),
+                      );
+                  }
               }
-              fn close(&mut self, _dd: DevDesc) {
-                  print_r_output(
-                      ::alloc::__export::must_use({
-                          let res = ::alloc::fmt::format(format_args!("good bye..."));
-                          res
-                      }),
+              #[allow(non_snake_case)]
+              fn meta__Wrapper__a_10(metadata: &mut Vec<extendr_api::metadata::Func>) {
+                  let args = <[_]>::into_vec(
+                      #[rustc_box]
+                      ::alloc::boxed::Box::new([
+                          extendr_api::metadata::Arg {
+                              name: "self",
+                              arg_type: "Wrapper",
+                              default: None,
+                          },
+                      ]),
                   );
-                  print_r_output("\n");
+                  metadata
+                      .push(extendr_api::metadata::Func {
+                          doc: "",
+                          rust_name: "a_10",
+                          r_name: "a_10",
+                          mod_name: "a_10",
+                          args: args,
+                          return_type: "i32",
+                          func_ptr: wrap__Wrapper__a_10 as *const u8,
+                          hidden: false,
+                      })
               }
-          }
-      }
-      mod matrix {
-          use extendr_api::prelude::*;
-          fn fetch_dimnames(x: RMatrix<f64>) -> List {
-              x.get_dimnames()
-          }
+              #[allow(non_snake_case)]
+              fn meta__Wrapper(impls: &mut Vec<extendr_api::metadata::Impl>) {
+                  let mut methods = Vec::new();
+                  meta__Wrapper__a_10(&mut methods);
+                  impls
+                      .push(extendr_api::metadata::Impl {
+                          doc: "",
+                          name: "Wrapper",
+                          methods,
+                      });
+              }
+              #[no_mangle]
+              #[allow(non_snake_case)]
+              pub fn get_submod_metadata() -> extendr_api::metadata::Metadata {
+                  let mut functions = Vec::new();
+                  let mut impls = Vec::new();
+                  meta__Wrapper(&mut impls);
+                  functions
+                      .push(extendr_api::metadata::Func {
+                          doc: "Metadata access function.",
+                          rust_name: "get_submod_metadata",
+                          mod_name: "get_submod_metadata",
+                          r_name: "get_submod_metadata",
+                          args: Vec::new(),
+                          return_type: "Metadata",
+                          func_ptr: wrap__get_submod_metadata as *const u8,
+                          hidden: true,
+                      });
+                  functions
+                      .push(extendr_api::metadata::Func {
+                          doc: "Wrapper generator.",
+                          rust_name: "make_submod_wrappers",
+                          mod_name: "make_submod_wrappers",
+                          r_name: "make_submod_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_submod_wrappers as *const u8,
+                          hidden: true,
+                      });
+                  extendr_api::metadata::Metadata {
+                      name: "submod",
+                      functions,
+                      impls,
+                  }
+              }
+              #[no_mangle]
+              #[allow(non_snake_case)]
+              pub extern "C" fn wrap__get_submod_metadata() -> extendr_api::SEXP {
+                  use extendr_api::GetSexp;
+                  unsafe { extendr_api::Robj::from(get_submod_metadata()).get() }
+              }
+              #[no_mangle]
+              #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+              pub extern "C" fn wrap__make_submod_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>::try_from(&robj).unwrap();
+                      let robj = Robj::from_sexp(package_name_sexp);
+                      let package_name: &str = <&str>::try_from(&robj).unwrap();
+                      extendr_api::Robj::from(
+                              get_submod_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_submod_extendr(info: *mut extendr_api::DllInfo) {
+                  unsafe { extendr_api::register_call_methods(info, get_submod_metadata()) };
+              }
+          }
+          enum Animal {
+              Cat,
+              Dog,
+          }
+          impl TryFrom<Robj> for &Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut Animal {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<Animal> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<Animal> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<Animal> for Robj {
+              fn from(value: Animal) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<Animal> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "Animal").unwrap();
+                      res.into()
+                  }
+              }
+          }
+          impl Animal {
+              pub fn new_dog() -> Self {
+                  Animal::Dog
+              }
+              pub fn new_cat() -> Self {
+                  Animal::Cat
+              }
+              pub fn speak(&self) -> Strings {
+                  match self {
+                      Animal::Dog => Strings::from("woof"),
+                      Animal::Cat => Strings::from("meow"),
+                  }
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__new_dog() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > { Ok(extendr_api::Robj::from(<Animal>::new_dog())) }),
+                  )
+              };
+              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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "new_dog"),
+                          );
+                          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__Animal__new_dog(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "new_dog",
+                      r_name: "new_dog",
+                      mod_name: "new_dog",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__Animal__new_dog as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__new_cat() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > { Ok(extendr_api::Robj::from(<Animal>::new_cat())) }),
+                  )
+              };
+              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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "new_cat"),
+                          );
+                          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__Animal__new_cat(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "new_cat",
+                      r_name: "new_cat",
+                      mod_name: "new_cat",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__Animal__new_cat as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__speak(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                          Ok(
+                              extendr_api::Robj::from(
+                                  extendr_api::unwrap_or_throw_error(
+                                          <&Animal>::try_from(&_self_robj),
+                                      )
+                                      .speak(),
+                              ),
+                          )
+                      }),
+                  )
+              };
+              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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "speak"),
+                          );
+                          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__Animal__speak(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "Animal",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "speak",
+                      r_name: "speak",
+                      mod_name: "speak",
+                      args: args,
+                      return_type: "Strings",
+                      func_ptr: wrap__Animal__speak as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[allow(non_snake_case)]
+          fn meta__Animal(impls: &mut Vec<extendr_api::metadata::Impl>) {
+              let mut methods = Vec::new();
+              meta__Animal__new_dog(&mut methods);
+              meta__Animal__new_cat(&mut methods);
+              meta__Animal__speak(&mut methods);
+              impls
+                  .push(extendr_api::metadata::Impl {
+                      doc: "",
+                      name: "Animal",
+                      methods,
+                  });
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_externalptr_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__create_numeric_externalptr(&mut functions);
+              meta__sum_integer_externalptr(&mut functions);
+              meta__Wrapper(&mut impls);
+              meta__Animal(&mut impls);
+              functions.extend(submod::get_submod_metadata().functions);
+              impls.extend(submod::get_submod_metadata().impls);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_externalptr_metadata",
+                      mod_name: "get_externalptr_metadata",
+                      r_name: "get_externalptr_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_externalptr_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_externalptr_wrappers",
+                      mod_name: "make_externalptr_wrappers",
+                      r_name: "make_externalptr_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_externalptr_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "externalptr",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_externalptr_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_externalptr_metadata()).get() }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__make_externalptr_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>::try_from(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::try_from(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_externalptr_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_externalptr_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_externalptr_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(
+                      ::alloc::__export::must_use({
+                          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(
+                      ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(format_args!("good bye..."));
+                          res
+                      }),
+                  );
+                  print_r_output("\n");
+              }
+          }
+      }
+      mod matrix {
+          use extendr_api::prelude::*;
+          fn fetch_dimnames(x: RMatrix<f64>) -> List {
+              x.get_dimnames()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__fetch_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(fetch_dimnames(_x_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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "fetch_dimnames"),
+                          );
+                          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__fetch_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "fetch_dimnames",
+                      r_name: "fetch_dimnames",
+                      mod_name: "fetch_dimnames",
+                      args: args,
+                      return_type: "List",
+                      func_ptr: wrap__fetch_dimnames as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn fetch_rownames(x: RMatrix<f64>) -> Option<Strings> {
+              x.get_rownames()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__fetch_rownames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(fetch_rownames(_x_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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "fetch_rownames"),
+                          );
+                          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__fetch_rownames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "fetch_rownames",
+                      r_name: "fetch_rownames",
+                      mod_name: "fetch_rownames",
+                      args: args,
+                      return_type: "Option",
+                      func_ptr: wrap__fetch_rownames as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn fetch_colnames(x: RMatrix<f64>) -> Option<Strings> {
+              x.get_colnames()
+          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__fetch_colnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -2886,7 +3553,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_dimnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(fetch_colnames(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -2903,7 +3570,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_dimnames"),
+                              format_args!("User function panicked: {0}", "fetch_colnames"),
                           );
                           res
                       });
@@ -2931,7 +3598,7 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__fetch_colnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
@@ -2945,21 +3612,27 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_dimnames",
-                      r_name: "fetch_dimnames",
-                      mod_name: "fetch_dimnames",
+                      rust_name: "fetch_colnames",
+                      r_name: "fetch_colnames",
+                      mod_name: "fetch_colnames",
                       args: args,
-                      return_type: "List",
-                      func_ptr: wrap__fetch_dimnames as *const u8,
+                      return_type: "Option",
+                      func_ptr: wrap__fetch_colnames as *const u8,
                       hidden: false,
                   })
           }
-          fn fetch_rownames(x: RMatrix<f64>) -> Option<Strings> {
-              x.get_rownames()
+          fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
+              let rownames = Strings::from_values(["AA", "BB", "CC"]);
+              x.set_dimnames(
+                  extendr_api::List::from_values(
+                      &[extendr_api::Robj::from(rownames), extendr_api::Robj::from(NULL)],
+                  ),
+              );
+              x.to_owned()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_rownames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__change_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -2971,7 +3644,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_rownames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(change_dimnames(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -2988,7 +3661,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_rownames"),
+                              format_args!("User function panicked: {0}", "change_dimnames"),
                           );
                           res
                       });
@@ -3016,7 +3689,7 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_rownames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__change_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
@@ -3030,21 +3703,21 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_rownames",
-                      r_name: "fetch_rownames",
-                      mod_name: "fetch_rownames",
+                      rust_name: "change_dimnames",
+                      r_name: "change_dimnames",
+                      mod_name: "change_dimnames",
                       args: args,
-                      return_type: "Option",
-                      func_ptr: wrap__fetch_rownames as *const u8,
+                      return_type: "Robj",
+                      func_ptr: wrap__change_dimnames as *const u8,
                       hidden: false,
                   })
           }
-          fn fetch_colnames(x: RMatrix<f64>) -> Option<Strings> {
-              x.get_colnames()
+          fn matrix_3d_return(x: RMatrix3D<f64>) -> RMatrix3D<f64> {
+              x
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_colnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__matrix_3d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -3056,7 +3729,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_colnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(matrix_3d_return(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -3073,7 +3746,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_colnames"),
+                              format_args!("User function panicked: {0}", "matrix_3d_return"),
                           );
                           res
                       });
@@ -3101,13 +3774,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_colnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__matrix_3d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "RMatrix",
+                          arg_type: "RMatrix3D",
                           default: None,
                       },
                   ]),
@@ -3115,27 +3788,106 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_colnames",
-                      r_name: "fetch_colnames",
-                      mod_name: "fetch_colnames",
+                      rust_name: "matrix_3d_return",
+                      r_name: "matrix_3d_return",
+                      mod_name: "matrix_3d_return",
                       args: args,
-                      return_type: "Option",
-                      func_ptr: wrap__fetch_colnames as *const u8,
+                      return_type: "RMatrix3D",
+                      func_ptr: wrap__matrix_3d_return as *const u8,
                       hidden: false,
                   })
           }
-          fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
-              let rownames = Strings::from_values(["AA", "BB", "CC"]);
-              x.set_dimnames(
-                  extendr_api::List::from_values(
-                      &[extendr_api::Robj::from(rownames), extendr_api::Robj::from(NULL)],
-                  ),
+          fn matrix_4d_return(x: RMatrix4D<f64>) -> RMatrix4D<f64> {
+              x
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__matrix_4d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(matrix_4d_return(_x_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 = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "matrix_4d_return"),
+                          );
+                          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__matrix_4d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix4D",
+                          default: None,
+                      },
+                  ]),
               );
-              x.to_owned()
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "matrix_4d_return",
+                      r_name: "matrix_4d_return",
+                      mod_name: "matrix_4d_return",
+                      args: args,
+                      return_type: "RMatrix4D",
+                      func_ptr: wrap__matrix_4d_return as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn matrix_5d_return(x: RMatrix5D<f64>) -> RMatrix5D<f64> {
+              x
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__change_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__matrix_5d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -3147,7 +3899,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(change_dimnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(matrix_5d_return(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -3164,7 +3916,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "change_dimnames"),
+                              format_args!("User function panicked: {0}", "matrix_5d_return"),
                           );
                           res
                       });
@@ -3192,13 +3944,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__change_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__matrix_5d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "RMatrix",
+                          arg_type: "RMatrix5D",
                           default: None,
                       },
                   ]),
@@ -3206,12 +3958,12 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "change_dimnames",
-                      r_name: "change_dimnames",
-                      mod_name: "change_dimnames",
+                      rust_name: "matrix_5d_return",
+                      r_name: "matrix_5d_return",
+                      mod_name: "matrix_5d_return",
                       args: args,
-                      return_type: "Robj",
-                      func_ptr: wrap__change_dimnames as *const u8,
+                      return_type: "RMatrix5D",
+                      func_ptr: wrap__matrix_5d_return as *const u8,
                       hidden: false,
                   })
           }
@@ -3224,6 +3976,9 @@
               meta__fetch_colnames(&mut functions);
               meta__fetch_rownames(&mut functions);
               meta__change_dimnames(&mut functions);
+              meta__matrix_3d_return(&mut functions);
+              meta__matrix_4d_return(&mut functions);
+              meta__matrix_5d_return(&mut functions);
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Metadata access function.",
@@ -5510,23 +6265,65 @@
                   );
               }
           }
-          #[allow(non_snake_case)]
-          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let args = ::alloc::vec::Vec::new();
-              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",
-                      args: args,
-                      return_type: "str",
-                      func_ptr: wrap__hello_submodule as *const u8,
-                      hidden: false,
-                  })
-          }
-          struct MySubmoduleClass {
-              a: i32,
+          #[allow(non_snake_case)]
+          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              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",
+                      args: args,
+                      return_type: "str",
+                      func_ptr: wrap__hello_submodule as *const u8,
+                      hidden: false,
+                  })
+          }
+          struct MySubmoduleClass {
+              a: i32,
+          }
+          impl TryFrom<Robj> for &MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<MySubmoduleClass> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<MySubmoduleClass> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<MySubmoduleClass> for Robj {
+              fn from(value: MySubmoduleClass) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<MySubmoduleClass> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
+                      res.into()
+                  }
+              }
           }
           #[automatically_derived]
           impl ::core::default::Default for MySubmoduleClass {
@@ -5840,48 +6637,6 @@
                       hidden: false,
                   })
           }
-          impl TryFrom<Robj> for &MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&robj)
-              }
-          }
-          impl TryFrom<Robj> for &mut MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&mut robj)
-              }
-          }
-          impl TryFrom<&Robj> for &MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &ExternalPtr<MySubmoduleClass> = robj.try_into()?;
-                      external_ptr.try_addr()
-                  }
-              }
-          }
-          impl TryFrom<&mut Robj> for &mut MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &mut ExternalPtr<MySubmoduleClass> = robj.try_into()?;
-                      external_ptr.try_addr_mut()
-                  }
-              }
-          }
-          impl From<MySubmoduleClass> for Robj {
-              fn from(value: MySubmoduleClass) -> Self {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let mut res: ExternalPtr<MySubmoduleClass> = ExternalPtr::new(value);
-                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
-                      res.into()
-                  }
-              }
-          }
           #[allow(non_snake_case)]
           fn meta__MySubmoduleClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -9734,6 +10489,48 @@
       struct MyClass {
           a: i32,
       }
+      impl TryFrom<Robj> for &MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+              Self::try_from(&robj)
+          }
+      }
+      impl TryFrom<Robj> for &mut MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+              Self::try_from(&mut robj)
+          }
+      }
+      impl TryFrom<&Robj> for &MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let external_ptr: &ExternalPtr<MyClass> = robj.try_into()?;
+                  external_ptr.try_addr()
+              }
+          }
+      }
+      impl TryFrom<&mut Robj> for &mut MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let external_ptr: &mut ExternalPtr<MyClass> = robj.try_into()?;
+                  external_ptr.try_addr_mut()
+              }
+          }
+      }
+      impl From<MyClass> for Robj {
+          fn from(value: MyClass) -> Self {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let mut res: ExternalPtr<MyClass> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "MyClass").unwrap();
+                  res.into()
+              }
+          }
+      }
       #[automatically_derived]
       impl ::core::default::Default for MyClass {
           #[inline]
@@ -10311,65 +11108,65 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &MyClass {
+      #[allow(non_snake_case)]
+      fn meta__MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
+          let mut methods = Vec::new();
+          meta__MyClass__new(&mut methods);
+          meta__MyClass__set_a(&mut methods);
+          meta__MyClass__a(&mut methods);
+          meta__MyClass__me(&mut methods);
+          meta__MyClass__restore_from_robj(&mut methods);
+          meta__MyClass__get_default_value(&mut methods);
+          impls
+              .push(extendr_api::metadata::Impl {
+                  doc: " Class for testing (exported)\n @examples\n x <- MyClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
+                  name: "MyClass",
+                  methods,
+              });
+      }
+      struct __MyClass {}
+      impl TryFrom<Robj> for &__MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&robj)
           }
       }
-      impl TryFrom<Robj> for &mut MyClass {
+      impl TryFrom<Robj> for &mut __MyClass {
           type Error = extendr_api::Error;
           fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&mut robj)
           }
       }
-      impl TryFrom<&Robj> for &MyClass {
+      impl TryFrom<&Robj> for &__MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &ExternalPtr<MyClass> = robj.try_into()?;
+                  let external_ptr: &ExternalPtr<__MyClass> = robj.try_into()?;
                   external_ptr.try_addr()
               }
           }
       }
-      impl TryFrom<&mut Robj> for &mut MyClass {
+      impl TryFrom<&mut Robj> for &mut __MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &mut ExternalPtr<MyClass> = robj.try_into()?;
+                  let external_ptr: &mut ExternalPtr<__MyClass> = robj.try_into()?;
                   external_ptr.try_addr_mut()
               }
           }
       }
-      impl From<MyClass> for Robj {
-          fn from(value: MyClass) -> Self {
+      impl From<__MyClass> for Robj {
+          fn from(value: __MyClass) -> Self {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let mut res: ExternalPtr<MyClass> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "MyClass").unwrap();
+                  let mut res: ExternalPtr<__MyClass> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "__MyClass").unwrap();
                   res.into()
               }
           }
       }
-      #[allow(non_snake_case)]
-      fn meta__MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
-          let mut methods = Vec::new();
-          meta__MyClass__new(&mut methods);
-          meta__MyClass__set_a(&mut methods);
-          meta__MyClass__a(&mut methods);
-          meta__MyClass__me(&mut methods);
-          meta__MyClass__restore_from_robj(&mut methods);
-          meta__MyClass__get_default_value(&mut methods);
-          impls
-              .push(extendr_api::metadata::Impl {
-                  doc: " Class for testing (exported)\n @examples\n x <- MyClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
-                  name: "MyClass",
-                  methods,
-              });
-      }
-      struct __MyClass {}
       #[automatically_derived]
       impl ::core::default::Default for __MyClass {
           #[inline]
@@ -10553,63 +11350,63 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &__MyClass {
+      #[allow(non_snake_case)]
+      fn meta____MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
+          let mut methods = Vec::new();
+          meta____MyClass__new(&mut methods);
+          meta____MyClass____name_test(&mut methods);
+          impls
+              .push(extendr_api::metadata::Impl {
+                  doc: "",
+                  name: "__MyClass",
+                  methods,
+              });
+      }
+      struct MyClassUnexported {
+          a: i32,
+      }
+      impl TryFrom<Robj> for &MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&robj)
           }
       }
-      impl TryFrom<Robj> for &mut __MyClass {
+      impl TryFrom<Robj> for &mut MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&mut robj)
           }
       }
-      impl TryFrom<&Robj> for &__MyClass {
+      impl TryFrom<&Robj> for &MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &ExternalPtr<__MyClass> = robj.try_into()?;
+                  let external_ptr: &ExternalPtr<MyClassUnexported> = robj.try_into()?;
                   external_ptr.try_addr()
               }
           }
       }
-      impl TryFrom<&mut Robj> for &mut __MyClass {
+      impl TryFrom<&mut Robj> for &mut MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &mut ExternalPtr<__MyClass> = robj.try_into()?;
+                  let external_ptr: &mut ExternalPtr<MyClassUnexported> = robj.try_into()?;
                   external_ptr.try_addr_mut()
               }
           }
       }
-      impl From<__MyClass> for Robj {
-          fn from(value: __MyClass) -> Self {
+      impl From<MyClassUnexported> for Robj {
+          fn from(value: MyClassUnexported) -> Self {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let mut res: ExternalPtr<__MyClass> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "__MyClass").unwrap();
+                  let mut res: ExternalPtr<MyClassUnexported> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "MyClassUnexported").unwrap();
                   res.into()
               }
           }
       }
-      #[allow(non_snake_case)]
-      fn meta____MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
-          let mut methods = Vec::new();
-          meta____MyClass__new(&mut methods);
-          meta____MyClass____name_test(&mut methods);
-          impls
-              .push(extendr_api::metadata::Impl {
-                  doc: "",
-                  name: "__MyClass",
-                  methods,
-              });
-      }
-      struct MyClassUnexported {
-          a: i32,
-      }
       #[automatically_derived]
       impl ::core::default::Default for MyClassUnexported {
           #[inline]
@@ -10803,48 +11600,6 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-              Self::try_from(&robj)
-          }
-      }
-      impl TryFrom<Robj> for &mut MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-              Self::try_from(&mut robj)
-          }
-      }
-      impl TryFrom<&Robj> for &MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let external_ptr: &ExternalPtr<MyClassUnexported> = robj.try_into()?;
-                  external_ptr.try_addr()
-              }
-          }
-      }
-      impl TryFrom<&mut Robj> for &mut MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let external_ptr: &mut ExternalPtr<MyClassUnexported> = robj.try_into()?;
-                  external_ptr.try_addr_mut()
-              }
-          }
-      }
-      impl From<MyClassUnexported> for Robj {
-          fn from(value: MyClassUnexported) -> Self {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let mut res: ExternalPtr<MyClassUnexported> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "MyClassUnexported").unwrap();
-                  res.into()
-              }
-          }
-      }
       #[allow(non_snake_case)]
       fn meta__MyClassUnexported(impls: &mut Vec<extendr_api::metadata::Impl>) {
           let mut methods = Vec::new();
diff --git a/tests/extendrtests/tests/testthat/test-extendr_impl.R b/tests/extendrtests/tests/testthat/test-extendr_impl.R
index baa510406..e8ecc118e 100644
--- a/tests/extendrtests/tests/testthat/test-extendr_impl.R
+++ b/tests/extendrtests/tests/testthat/test-extendr_impl.R
@@ -1,4 +1,4 @@
-test_that("return self reference",  {
+test_that("return self reference", {
   x <- Wrapper$new()
   x$set_a(10)
 
@@ -49,3 +49,17 @@ test_that("return the right reference with other parameters present", {
   expect_identical(max_class, y)
   expect_false(isTRUE(identical(x, max_class)))
 })
+
+test_that("submodule impl for Wrapper exists", {
+  x <- Wrapper$new()
+  x$set_a(10)
+  expect_identical(x$a_10(), 20L)
+})
+
+test_that("enum wrapper for Animal works", {
+  cat <- Animal$new_cat()
+  expect_identical(cat$speak(), "meow")
+  dog <- Animal$new_dog()
+  expect_identical(dog$speak(), "woof")
+  expect_false(identical(cat, dog))
+})
diff --git a/tests/extendrtests/tests/testthat/test-matrix.R b/tests/extendrtests/tests/testthat/test-matrix.R
index 93edf9ba8..3f7228e9f 100644
--- a/tests/extendrtests/tests/testthat/test-matrix.R
+++ b/tests/extendrtests/tests/testthat/test-matrix.R
@@ -23,3 +23,28 @@ test_that("Dimnames can be changed", {
   dimnames(m2) <- list(c("AA", "BB", "CC"), NULL)
   expect_equal(change_dimnames(m), m2)
 })
+
+test_that("Matrix3D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4)), dim = c(2, 3, 4))
+  expect_equal(matrix_3d_return(m3d), m3d)
+})
+
+test_that("Matrix4D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4*5)), dim = c(2, 3, 4, 5))
+  expect_equal(matrix_4d_return(m3d), m3d)
+})
+
+test_that("Matrix4D should error when dimensions are different", {
+  m3d <- array(as.double(1:(2*3*4*5*6)), dim = c(2, 3, 4, 5, 6))
+  expect_error(matrix_4d_return(m3d))
+})
+
+test_that("Matrix5D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4*5*6)), dim = c(2, 3, 4, 5, 6))
+  expect_equal(matrix_5d_return(m3d), m3d)
+})
+
+test_that("Matrix5D should error when dimensions are different", {
+  m3d <- array(as.double(1:(2*3*4*5)), dim = c(2, 3, 4, 5))
+  expect_error(matrix_5d_return(m3d))
+})
diff --git a/tests/extendrtests/tools/msrv.R b/tests/extendrtests/tools/msrv.R
new file mode 100644
index 000000000..59a61ab2e
--- /dev/null
+++ b/tests/extendrtests/tools/msrv.R
@@ -0,0 +1,116 @@
+# read the DESCRIPTION file
+desc <- read.dcf("DESCRIPTION")
+
+if (!"SystemRequirements" %in% colnames(desc)) {
+  fmt <- c(
+    "`SystemRequirements` not found in `DESCRIPTION`.",
+    "Please specify `SystemRequirements: Cargo (Rust's package manager), rustc`"
+  )
+  stop(paste(fmt, collapse = "\n"))
+}
+
+# extract system requirements
+sysreqs <- desc[, "SystemRequirements"]
+
+# check that cargo and rustc is found
+if (!grepl("cargo", sysreqs, ignore.case = TRUE)) {
+  stop("You must specify `Cargo (Rust's package manager)` in your `SystemRequirements`")
+}
+
+if (!grepl("rustc", sysreqs, ignore.case = TRUE)) {
+  stop("You must specify `Cargo (Rust's package manager), rustc` in your `SystemRequirements`")
+}
+
+# split into parts
+parts <- strsplit(sysreqs, ", ")[[1]]
+
+# identify which is the rustc
+rustc_ver <- parts[grepl("rustc", parts)]
+
+# perform checks for the presence of rustc and cargo on the OS
+no_cargo_msg <- c(
+  "----------------------- [CARGO NOT FOUND]--------------------------",
+  "The 'cargo' command was not found on the PATH. Please install Cargo",
+  "from: https://www.rust-lang.org/tools/install",
+  "",
+  "Alternatively, you may install Cargo from your OS package manager:",
+  " - Debian/Ubuntu: apt-get install cargo",
+  " - Fedora/CentOS: dnf install cargo",
+  " - macOS: brew install rust",
+  "-------------------------------------------------------------------"
+)
+
+no_rustc_msg <- c(
+  "----------------------- [RUST NOT FOUND]---------------------------",
+  "The 'rustc' compiler was not found on the PATH. Please install",
+  paste(rustc_ver, "or higher from:"),
+  "https://www.rust-lang.org/tools/install",
+  "",
+  "Alternatively, you may install Rust from your OS package manager:",
+  " - Debian/Ubuntu: apt-get install rustc",
+  " - Fedora/CentOS: dnf install rustc",
+  " - macOS: brew install rust",
+  "-------------------------------------------------------------------"
+)
+
+# Add {user}/.cargo/bin to path before checking
+new_path <- paste0(
+  Sys.getenv("PATH"),
+  ":",
+  paste0(Sys.getenv("HOME"), "/.cargo/bin")
+)
+
+# set the path with the new path
+Sys.setenv("PATH" = new_path)
+
+# check for rustc installation
+rustc_version <- tryCatch(
+  system("rustc --version", intern = TRUE),
+  error = function(e) {
+    stop(paste(no_rustc_msg, collapse = "\n"))
+  }
+)
+
+# check for cargo installation
+cargo_version <- tryCatch(
+  system("cargo --version", intern = TRUE),
+  error = function(e) {
+    stop(paste(no_cargo_msg, collapse = "\n"))
+  }
+)
+
+# helper function to extract versions
+extract_semver <- function(ver) {
+  if (grepl("\\d+\\.\\d+(\\.\\d+)?", ver)) {
+    sub(".*?(\\d+\\.\\d+(\\.\\d+)?).*", "\\1", ver)
+  } else {
+    NA
+  }
+}
+
+# get the MSRV
+msrv <- extract_semver(rustc_ver)
+
+# extract current version
+current_rust_version <- extract_semver(rustc_version)
+
+# perform check
+if (!is.na(msrv)) {
+  # -1 when current version is later
+  # 0 when they are the same
+  # 1 when MSRV is newer than current
+  is_msrv <- utils::compareVersion(msrv, current_rust_version)
+  if (is_msrv == 1) {
+    fmt <- paste0(
+      "\n------------------ [UNSUPPORTED RUST VERSION]------------------\n",
+      "- Minimum supported Rust version is %s.\n",
+      "- Installed Rust version is %s.\n",
+      "---------------------------------------------------------------"
+    )
+    stop(sprintf(fmt, msrv, current_rust_version))
+  }
+}
+
+# print the versions
+versions_fmt <- "Using %s\nUsing %s"
+message(sprintf(versions_fmt, cargo_version, rustc_version))