Repository: extendr/extendr
Pull request #600 (2023-08-29 00:52:50)

Add ALTLIST 
ALTLIST representations were recently added to base R. 
This PR adds functionality to create an ALTREP for List objects. 



JosiahParry: I think I may have messed up my fork sync. If that's the case let me know and I will blow it all up and make a new branch :P 
JosiahParry: The CI errors are due to not finding the ALTLIST functions in libRsys which should be available for R 4.1 onwards. They're present in 0.6.0 as well. https://docs.rs/libR-sys/latest/libR_sys/?search=altlist
yutannihilation: Yes, one small correction is the version with ALTLIST support is R 4.3.
CGMossa: @JosiahParry I hope it is okay: But I added the feature-gate. Let's see if it passes tests. 
JosiahParry: I only just finished eating breakfast! Y'all are fast. Thank you both for the comments. @CGMossa i see you pushed some commits to address them. Thank you!
JosiahParry: @yutannihilation pushed!

Patch:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 089f08a93..07f365bd9 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -11,7 +11,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@master
         with:
           toolchain: nightly
diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml
index 8c8621f43..2e59e1897 100644
--- a/.github/workflows/msrv.yml
+++ b/.github/workflows/msrv.yml
@@ -25,7 +25,7 @@ jobs:
   test-min-rust-version:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - uses: baptiste0928/cargo-install@v2
         with:
           crate: cargo-msrv
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e9a75874e..ec9d1f187 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,12 @@
 
 ### Added
 
+- `ALTLIST` support allowing users to represent structs as R list objects
 - [**either**] `TryFrom<&Robj> for Either<T, R>` and `From<Either<T, R>> for Robj` if `T` and `R` are themselves implement these traits. This unblocks scenarios like accepting any numeric vector from R via `Either<Integers, Doubles>` without extra memory allocation [[#480]](https://github.com/extendr/extendr/pull/480)
 - `PartialOrd` trait implementation for `Rfloat`, `Rint` and `Rbool`. `Rfloat` and `Rint` gained `min()` and `max()` methods [[#573]](https://github.com/extendr/extendr/pull/573)
+- `use_rng` option for the `extendr` attribute macro, which enables the use of
+random number sampling methods from R, e.g. `#[extendr(use_rng = true)` [[#476]](https://github.com/extendr/extendr/pull/476)
+- `[T; N]` conversions to `Robj` [[#594]](https://github.com/extendr/extendr/pull/594/)
 
 ### Fixed
 
diff --git a/Cargo.toml b/Cargo.toml
index 6a2829eb1..6c5bda2d9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,10 @@
 [workspace]
 resolver = "2"
 
-members = [
-	"extendr-api",
-	"extendr-engine",
-	"extendr-macros",
-]
+members = ["extendr-api", "extendr-engine", "extendr-macros"]
+
+[workspace.dependencies]
+libR-sys = "0.6.0"
 
 [patch.crates-io]
 # When uncommenting this, do not forget to uncomment the same line in
diff --git a/extendr-api/Cargo.toml b/extendr-api/Cargo.toml
index 00bc186d2..c6ddf54dc 100644
--- a/extendr-api/Cargo.toml
+++ b/extendr-api/Cargo.toml
@@ -17,12 +17,11 @@ repository = "https://github.com/extendr/extendr"
 rust-version = "1.60"
 
 [dependencies]
-libR-sys = "0.6.0"
-extendr-macros = { path = "../extendr-macros", version = "0.4.0" }
-extendr-engine = { path = "../extendr-engine", version = "0.4.0" }
+libR-sys = { workspace = true }
+extendr-macros = { path = "../extendr-macros", version="0.4.0" }
+extendr-engine = { path = "../extendr-engine", version="0.4.0" }
 lazy_static = "1.4"
 paste = "1.0.5"
-
 either = { version = "1.8.1", optional = true }
 libc = { version = "0.2", optional = true }
 ndarray = { version = "0.15.3", optional = true }
diff --git a/extendr-api/build.rs b/extendr-api/build.rs
index a8397a40f..ff884961f 100644
--- a/extendr-api/build.rs
+++ b/extendr-api/build.rs
@@ -26,4 +26,8 @@ fn main() {
     if &*major >= "4" && &*minor >= "2" {
         println!("cargo:rustc-cfg=use_r_ge_version_15");
     }
+
+    if &*major >= "4" && &*minor >= "3" {
+        println!("cargo:rustc-cfg=use_r_altlist");
+    }
 }
diff --git a/extendr-api/src/graphics/mod.rs b/extendr-api/src/graphics/mod.rs
index 5a8674cfd..154255da6 100644
--- a/extendr-api/src/graphics/mod.rs
+++ b/extendr-api/src/graphics/mod.rs
@@ -702,7 +702,7 @@ impl Device {
 
     /// Screen capture. Returns an integer matrix representing pixels if it is able.
     pub fn capture(&self) -> Robj {
-        unsafe { new_owned(GECap(self.inner())) }
+        unsafe { Robj::from_sexp(GECap(self.inner())) }
     }
 
     /// Draw a bitmap.
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 47aafcf35..7caa1cf6d 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -294,26 +294,26 @@
 //! In R:
 //!
 //! ```ignore
-//! #default result_panic feature
+//! # default result_panic feature
 //! oups(1)
 //! > ... long panic traceback from rust printed to stderr
 //!
-//! #result_list feature
-//! lst <-oups(1)
+//! # result_list feature
+//! lst <- oups(1)
 //! print(lst)
-//! > list(ok=NULL, err="I did it again")
+//! > list(ok = NULL, err = "I did it again")
 //!
-//! #result_condition feature
-//! cnd = oups(1)
+//! # result_condition feature
+//! cnd <- oups(1)
 //! print(cnd)
 //! > <error: extendr_error>
 //! print(cnd$value)
 //! > "I did it again"
 //!
-//! #handling example for result_condition
-//! oups_handled = function(a) {
-//!   val_or_err = oups(1)  
-//!   if(inherits(val_or_err,"extendr_error")) stop(val_or_err)
+//! # handling example for result_condition
+//! oups_handled <- function(a) {
+//!   val_or_err <- oups(1)  
+//!   if (inherits(val_or_err, "extendr_error")) stop(val_or_err)
 //!   val_or_err
 //! }
 //!
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index 0dc303d32..7b879df37 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -663,38 +663,6 @@ where
     }
 }
 
-// We would love to do a blanket IntoIterator impl.
-// But the matching rules would clash with the above.
-macro_rules! impl_from_iter {
-    ($t: ty) => {
-        impl<'a, T> From<$t> for Robj
-        where
-            Self: 'a,
-            T: Clone + 'a,
-            T: ToVectorValue,
-        {
-            fn from(val: $t) -> Self {
-                val.iter().cloned().collect_robj()
-            }
-        }
-    };
-}
-
-macro_rules! impl_from_into_iter {
-    ($t: ty) => {
-        impl<'a, T> From<$t> for Robj
-        where
-            Self: 'a,
-            T: 'a,
-            &'a T: ToVectorValue,
-        {
-            fn from(val: $t) -> Self {
-                val.into_iter().collect_robj()
-            }
-        }
-    };
-}
-
 macro_rules! impl_from_as_iterator {
     ($t: ty) => {
         impl<T> From<$t> for Robj
@@ -721,30 +689,60 @@ macro_rules! impl_from_as_iterator {
 //     }
 // } //
 
-// Template constants are still unstable in rust.
-impl_from_iter! {[T; 1]}
-impl_from_iter! {[T; 2]}
-impl_from_iter! {[T; 3]}
-impl_from_iter! {[T; 4]}
-impl_from_iter! {[T; 5]}
-impl_from_iter! {[T; 6]}
-impl_from_iter! {[T; 7]}
-impl_from_iter! {[T; 8]}
-impl_from_iter! {[T; 9]}
-impl_from_iter! {[T; 10]}
-impl_from_iter! {[T; 11]}
-impl_from_iter! {[T; 12]}
-impl_from_iter! {[T; 13]}
-impl_from_iter! {[T; 14]}
-impl_from_iter! {[T; 15]}
-impl_from_iter! {[T; 16]}
-impl_from_iter! {[T; 17]}
-impl_from_iter! {[T; 18]}
-impl_from_iter! {[T; 19]}
-impl_from_iter! {Vec<T>}
-impl_from_iter! {&Vec<T>}
-
-impl_from_into_iter! {&'a [T]}
+impl<'a, T, const N: usize> From<[T; N]> for Robj
+where
+    Self: 'a,
+    T: ToVectorValue,
+{
+    fn from(val: [T; N]) -> Self {
+        fixed_size_collect(val.into_iter(), N)
+    }
+}
+
+impl<'a, T, const N: usize> From<&'a [T; N]> for Robj
+where
+    Self: 'a,
+    &'a T: ToVectorValue + 'a,
+{
+    fn from(val: &'a [T; N]) -> Self {
+        fixed_size_collect(val.into_iter(), N)
+    }
+}
+
+impl<'a, T, const N: usize> From<&'a mut [T; N]> for Robj
+where
+    Self: 'a,
+    &'a mut T: ToVectorValue + 'a,
+{
+    fn from(val: &'a mut [T; N]) -> Self {
+        fixed_size_collect(val.into_iter(), N)
+    }
+}
+
+impl<T: ToVectorValue + Clone> From<&Vec<T>> for Robj {
+    fn from(value: &Vec<T>) -> Self {
+        let len = value.len();
+        fixed_size_collect(value.iter().cloned(), len)
+    }
+}
+
+impl<T: ToVectorValue> From<Vec<T>> for Robj {
+    fn from(value: Vec<T>) -> Self {
+        let len = value.len();
+        fixed_size_collect(value.into_iter(), len)
+    }
+}
+
+impl<'a, T> From<&'a [T]> for Robj
+where
+    Self: 'a,
+    T: 'a,
+    &'a T: ToVectorValue,
+{
+    fn from(val: &'a [T]) -> Self {
+        val.into_iter().collect_robj()
+    }
+}
 
 impl_from_as_iterator! {Range<T>}
 impl_from_as_iterator! {RangeInclusive<T>}
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index d9c82f875..d4d264771 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -845,7 +845,6 @@ pub trait Attributes: Types + Length {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///
     ///    let mut robj = r!("hello").set_attrib(sym!(xyz), 1)?;
     ///    assert_eq!(robj.get_attrib(sym!(xyz)), Some(r!(1)));
     /// }
@@ -1027,14 +1026,6 @@ pub trait Attributes: Types + Length {
 
 impl Attributes for Robj {}
 
-#[doc(hidden)]
-pub unsafe fn new_owned(sexp: SEXP) -> Robj {
-    single_threaded(|| {
-        ownership::protect(sexp);
-        Robj { inner: sexp }
-    })
-}
-
 /// Compare equality with integer slices.
 impl PartialEq<[i32]> for Robj {
     fn eq(&self, rhs: &[i32]) -> bool {
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 5516c32c5..b1ccd73ca 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -471,6 +471,12 @@ pub trait Rinternals: Types + Conversions {
         unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == STRSXP as i32 }
     }
 
+    /// Returns `true` if this is an integer ALTREP object.
+    #[cfg(use_r_altlist)]
+    fn is_altlist(&self) -> bool {
+        unsafe { ALTREP(self.get()) != 0 && TYPEOF(self.get()) == VECSXP as i32 }
+    }
+
     /// Generate a text representation of this object.
     fn deparse(&self) -> Result<String> {
         use crate as extendr_api;
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index 49ff3e026..cefcfb341 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -455,6 +455,16 @@ pub trait AltStringImpl {
     }
 }
 
+#[cfg(use_r_altlist)]
+pub trait AltListImpl {
+    /// Get a single element from this vector
+    /// a single element of a list can be any Robj
+    fn elt(&self, _index: usize) -> Robj;
+
+    /// Set a single element in this list.
+    fn set_elt(&mut self, _index: usize, _value: Robj) {}
+}
+
 impl Altrep {
     /// Safely implement R_altrep_data1, R_altrep_data2.
     /// When implementing Altrep classes, this gets the metadata.
@@ -496,9 +506,9 @@ impl Altrep {
             }
 
             let ptr: *mut StateType = Box::into_raw(Box::new(state));
-            let tag = r!(());
-            let prot = r!(());
-            let state = R_MakeExternalPtr(ptr as *mut c_void, tag.get(), prot.get());
+            let tag = R_NilValue;
+            let prot = R_NilValue;
+            let state = R_MakeExternalPtr(ptr as *mut c_void, tag, prot);
             R_RegisterCFinalizer(state, Some(finalizer::<StateType>));
 
             let class_ptr = R_altrep_class_t { ptr: class.get() };
@@ -657,6 +667,10 @@ impl Altrep {
                 Rtype::Strings => {
                     R_make_altstring_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
                 }
+                #[cfg(use_r_altlist)]
+                Rtype::List => {
+                    R_make_altlist_class(csname.as_ptr(), csbase.as_ptr(), std::ptr::null_mut())
+                }
                 _ => panic!("expected Altvec compatible type"),
             };
 
@@ -956,7 +970,7 @@ impl Altrep {
         })
     }
 
-    /// Make a complex ALTREP class that can be used to make vectors.
+    /// Make a string ALTREP class that can be used to make vectors.
     pub fn make_altstring_class<StateType: AltrepImpl + AltStringImpl + 'static>(
         name: &str,
         base: &str,
@@ -1005,6 +1019,39 @@ impl Altrep {
         })
     }
 
+    #[cfg(use_r_altlist)]
+    pub fn make_altlist_class<StateType: AltrepImpl + AltListImpl + 'static>(
+        name: &str,
+        base: &str,
+    ) -> Robj {
+        #![allow(non_snake_case)]
+
+        single_threaded(|| unsafe {
+            let class = Altrep::altrep_class::<StateType>(Rtype::List, name, base);
+            let class_ptr = R_altrep_class_t { ptr: class.get() };
+
+            unsafe extern "C" fn altlist_Elt<StateType: AltListImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+            ) -> SEXP {
+                Altrep::get_state::<StateType>(x).elt(i as usize).get()
+            }
+
+            unsafe extern "C" fn altlist_Set_elt<StateType: AltListImpl + 'static>(
+                x: SEXP,
+                i: R_xlen_t,
+                v: SEXP,
+            ) {
+                Altrep::get_state_mut::<StateType>(x)
+                    .set_elt(i as usize, Robj::from_sexp(v).try_into().unwrap())
+            }
+
+            R_set_altlist_Elt_method(class_ptr, Some(altlist_Elt::<StateType>));
+            R_set_altlist_Set_elt_method(class_ptr, Some(altlist_Set_elt::<StateType>));
+            class
+        })
+    }
+
     make_from_iterator!(
         make_altinteger_from_iterator,
         make_altinteger_class,
diff --git a/extendr-api/src/wrapper/externalptr.rs b/extendr-api/src/wrapper/externalptr.rs
index 619bb29ee..af348a60a 100644
--- a/extendr-api/src/wrapper/externalptr.rs
+++ b/extendr-api/src/wrapper/externalptr.rs
@@ -94,9 +94,15 @@ impl<T: Any + Debug> ExternalPtr<T> {
                 unsafe {
                     let ptr = R_ExternalPtrAddr(x) as *mut T;
 
+                    // Free the `tag`, which is the type-name
+                    R_SetExternalPtrTag(x, R_NilValue);
+
                     // Convert the pointer to a box and drop it implictly.
                     // This frees up the memory we have used and calls the "T::drop" method if there is one.
                     drop(Box::from_raw(ptr));
+
+                    // Now set the pointer in ExternalPTR to C `NULL`
+                    R_ClearExternalPtr(x);
                 }
             }
 
@@ -115,12 +121,12 @@ impl<T: Any + Debug> ExternalPtr<T> {
 
     /// Get the "tag" of an external pointer. This is the type name in the common case.
     pub fn tag(&self) -> Robj {
-        unsafe { new_owned(R_ExternalPtrTag(self.robj.get())) }
+        unsafe { Robj::from_sexp(R_ExternalPtrTag(self.robj.get())) }
     }
 
     /// Get the "protected" field of an external pointer. This is NULL in the common case.
     pub fn protected(&self) -> Robj {
-        unsafe { new_owned(R_ExternalPtrProtected(self.robj.get())) }
+        unsafe { Robj::from_sexp(R_ExternalPtrProtected(self.robj.get())) }
     }
 
     /// Get the "address" field of an external pointer.
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index a66f542e9..948654f52 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -30,6 +30,8 @@ pub mod strings;
 pub mod symbol;
 
 pub use self::rstr::Rstr;
+#[cfg(use_r_altlist)]
+pub use altrep::AltListImpl;
 pub use altrep::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
     AltrepImpl,
diff --git a/extendr-macros/Cargo.toml b/extendr-macros/Cargo.toml
index 466146302..cfb901a9c 100644
--- a/extendr-macros/Cargo.toml
+++ b/extendr-macros/Cargo.toml
@@ -7,7 +7,7 @@ authors = [
     "Mossa Merhi Reimert <mossa@sund.ku.dk>",
     "Claus O. Wilke <wilke@austin.utexas.edu>",
     "Hiroaki Yutani",
-    "Ilia A. Kosenkov <ilia.kosenkov@outlook.com>"
+    "Ilia A. Kosenkov <ilia.kosenkov@outlook.com>",
 ]
 edition = "2021"
 description = "Generate bindings from R to Rust."
@@ -25,3 +25,4 @@ proc-macro2 = { version = "1.0" }
 [dev-dependencies]
 extendr-api = { path = "../extendr-api" }
 extendr-engine = { path = "../extendr-engine" }
+libR-sys = { workspace = true }
diff --git a/extendr-macros/src/extendr_function.rs b/extendr-macros/src/extendr_function.rs
index 25ba3e0ab..2dd5c3775 100644
--- a/extendr-macros/src/extendr_function.rs
+++ b/extendr-macros/src/extendr_function.rs
@@ -10,7 +10,6 @@ pub fn extendr_function(args: Vec<syn::NestedMeta>, mut func: ItemFn) -> TokenSt
     for arg in &args {
         parse_options(&mut opts, arg);
     }
-
     let mut wrappers: Vec<ItemFn> = Vec::new();
     wrappers::make_function_wrappers(&opts, &mut wrappers, "", &func.attrs, &mut func.sig, None);
 
@@ -21,12 +20,19 @@ pub fn extendr_function(args: Vec<syn::NestedMeta>, mut func: ItemFn) -> TokenSt
     })
 }
 
-/// Parse a set of attribute arguments for #[extendr(opts...)]
+/// Parse a set of attribute arguments for `#[extendr(opts...)]`
+///
+/// Supported options:
+///
+/// - `use_try_from = bool` which uses `TryFrom<Robj>` for argument conversions.
+/// - `r_name = "name"` which specifies the name of the wrapper on the R-side.
+/// - `use_rng = bool` ensures the RNG-state is pulled and pushed
+///
 pub fn parse_options(opts: &mut wrappers::ExtendrOptions, arg: &syn::NestedMeta) {
     use syn::{Lit, LitBool, Meta, MetaNameValue, NestedMeta};
 
     fn help_message() -> ! {
-        panic!("expected #[extendr(use_try_from=bool, r_name=\"name\")]");
+        panic!("expected #[extendr(use_try_from = bool, r_name = \"name\", mod_name = \"r_mod_name\", use_rng = bool)]");
     }
 
     match arg {
@@ -53,6 +59,12 @@ pub fn parse_options(opts: &mut wrappers::ExtendrOptions, arg: &syn::NestedMeta)
                 } else {
                     help_message();
                 }
+            } else if path.is_ident("use_rng") {
+                if let Lit::Bool(LitBool { value, .. }) = lit {
+                    opts.use_rng = *value;
+                } else {
+                    help_message();
+                }
             } else {
                 help_message();
             }
diff --git a/extendr-macros/src/extendr_impl.rs b/extendr-macros/src/extendr_impl.rs
index dbdf84d9f..8e2dca3f2 100644
--- a/extendr-macros/src/extendr_impl.rs
+++ b/extendr-macros/src/extendr_impl.rs
@@ -165,7 +165,7 @@ pub fn extendr_impl(mut item_impl: ItemImpl) -> TokenStream {
         // Function to free memory for this type.
         extern "C" fn #finalizer_name (sexp: extendr_api::SEXP) {
             unsafe {
-                let robj = extendr_api::new_owned(sexp);
+                let robj = extendr_api::robj::Robj::from_sexp(sexp);
                 if robj.check_external_ptr_type::<#self_ty>() {
                     //eprintln!("finalize {}", #self_ty_name);
                     let ptr = robj.external_ptr_addr::<#self_ty>();
diff --git a/extendr-macros/src/extendr_module.rs b/extendr-macros/src/extendr_module.rs
index d6f003454..3c929733c 100644
--- a/extendr-macros/src/extendr_module.rs
+++ b/extendr-macros/src/extendr_module.rs
@@ -101,10 +101,10 @@ pub fn extendr_module(item: TokenStream) -> TokenStream {
             unsafe {
                 use extendr_api::robj::*;
                 use extendr_api::GetSexp;
-                let robj = new_owned(use_symbols_sexp);
+                let robj = Robj::from_sexp(use_symbols_sexp);
                 let use_symbols: bool = <bool>::from_robj(&robj).unwrap();
 
-                let robj = new_owned(package_name_sexp);
+                let robj = Robj::from_sexp(package_name_sexp);
                 let package_name: &str = <&str>::from_robj(&robj).unwrap();
 
                 extendr_api::Robj::from(
diff --git a/extendr-macros/src/wrappers.rs b/extendr-macros/src/wrappers.rs
index bc7576615..d09173e0b 100644
--- a/extendr-macros/src/wrappers.rs
+++ b/extendr-macros/src/wrappers.rs
@@ -10,6 +10,7 @@ pub struct ExtendrOptions {
     pub use_try_from: bool,
     pub r_name: Option<String>,
     pub mod_name: Option<String>,
+    pub use_rng: bool,
 }
 
 // Generate wrappers for a specific function.
@@ -100,12 +101,31 @@ pub fn make_function_wrappers(
     //     }
     // }
     // ```
-    //
+    let rng_start = opts
+        .use_rng
+        .then(|| {
+            quote!(unsafe {
+                libR_sys::GetRNGstate();
+            })
+        })
+        .unwrap_or_default();
+    let rng_end = opts
+        .use_rng
+        .then(|| {
+            quote!(unsafe {
+                libR_sys::PutRNGstate();
+            })
+        })
+        .unwrap_or_default();
     wrappers.push(parse_quote!(
         #[no_mangle]
         #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
         pub extern "C" fn #wrap_name(#formal_args) -> extendr_api::SEXP {
             use extendr_api::robj::*;
+
+            // pull RNG state before evaluation
+            #rng_start
+
             let wrap_result_state: std::result::Result<
                 std::result::Result<Robj, extendr_api::Error>,
                 Box<dyn std::any::Any + Send>
@@ -115,9 +135,11 @@ pub fn make_function_wrappers(
                     Ok(extendr_api::Robj::from(#call_name(#actual_args)))
                 })
             };
-            // any obj created in above unsafe scope, which are not moved into wrap_result_state are now dropped
 
+            // return RNG state back to r after evaluation
+            #rng_end
 
+            // any obj created in above unsafe scope, which are not moved into wrap_result_state are now dropped
             match wrap_result_state {
                 Ok(Ok(zz)) => {
                     return unsafe { zz.get() };
@@ -311,13 +333,13 @@ fn translate_to_robj(input: &FnArg) -> syn::Stmt {
             let pat = &pattype.pat.as_ref();
             if let syn::Pat::Ident(ref ident) = pat {
                 let varname = format_ident!("_{}_robj", ident.ident);
-                parse_quote! { let #varname = extendr_api::new_owned(#pat); }
+                parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#pat); }
             } else {
                 panic!("expect identifier as arg name")
             }
         }
         FnArg::Receiver(_) => {
-            parse_quote! { let mut _self_robj = extendr_api::new_owned(_self); }
+            parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); }
         }
     }
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 871275f3b..1eaeba94c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -61,7 +61,7 @@ jobs:
         shell: pwsh
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       
       - name: Set up Rust
         uses: dtolnay/rust-toolchain@master
@@ -162,7 +162,7 @@ jobs:
 
       # c.f. https://github.com/actions/checkout#checkout-multiple-repos-side-by-side
       - name: Obtain 'rextendr'
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           repository: extendr/rextendr
           path: ./tests/rextendr
@@ -296,7 +296,7 @@ jobs:
         shell: pwsh
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Set up Rust
         uses: dtolnay/rust-toolchain@master
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index 40ef81126..1d269530d 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -1,4 +1,5 @@
 use extendr_api::prelude::*;
+use extendr_engine::with_r;
 
 #[test]
 fn test_altinteger() {
@@ -231,7 +232,51 @@ fn test_altstring() {
         let class = Altrep::make_altstring_class::<StringInts>("si", "mypkg");
         let obj = Altrep::from_state_and_class(mystate, class, false);
 
+        assert!(obj.is_altstring());
         assert_eq!(obj.len(), 10);
         assert_eq!(Robj::from(obj), r!(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]));
     }
 }
+
+#[test]
+#[cfg(use_r_altlist)]
+fn test_altlist() {
+    use extendr_api::AltListImpl;
+    with_r(|| {
+        #[derive(Debug, Clone)]
+        pub struct VecUsize(pub Vec<Option<usize>>);
+
+        // need to make the VecUsize object `.into_robj()`-able
+        #[extendr]
+        impl VecUsize {}
+
+        impl AltrepImpl for VecUsize {
+            fn length(&self) -> usize {
+                self.0.len()
+            }
+        }
+
+        impl AltListImpl for VecUsize {
+            fn elt(&self, index: usize) -> Robj {
+                Self(vec![self.0[index]]).into_robj()
+            }
+        }
+
+        let vu = VecUsize(vec![Some(1), None, Some(10)]);
+
+        let class = Altrep::make_altlist_class::<VecUsize>("li", "mypkg");
+        let obj = Altrep::from_state_and_class(vu, class, false);
+
+        // confirm it is altlist
+        assert!(obj.is_altlist());
+
+        // confirm method is accurate
+        assert_eq!(obj.len(), 3);
+
+        // convert to a list and test the .elt() method
+        let l = List::try_from(obj.into_robj()).unwrap();
+        let li = l.elt(1).unwrap();
+
+        assert!(li.inherits("VecUsize"));
+    })
+}
diff --git a/extendr-api/tests/rng_tests.rs b/extendr-api/tests/rng_tests.rs
new file mode 100644
index 000000000..8cf2f15cd
--- /dev/null
+++ b/extendr-api/tests/rng_tests.rs
@@ -0,0 +1,40 @@
+//! Tests the ability to use R to generate random numbers.
+//!
+//! The `extendr` attribute macro now has a `use_rng` argument.
+//! If this is used, then a `GetRNGstate()` is invoked before the function is called,
+//! and a `PutRNGstate()` is invoked after, even if said method panics.
+//!
+//! These tests has to check two things:
+//!
+//! * That the seed is set and affects the sampling functions on the rust-side
+//! in the same way, as on the r-side.
+//! * That the resulting sampled vector is not equal 0, as it would be that
+//! if `GetRNGstate()` is not called prior to sampling.
+//!
+//!
+use extendr_api::prelude::*;
+
+#[extendr(use_rng = true)]
+fn generate_big_random_vec() -> Vec<f64> {
+    let n = 100;
+    let param = 10e3;
+    (0..n)
+        .map(|_| unsafe { libR_sys::R_unif_index(param) })
+        .collect()
+}
+
+#[test]
+fn test_extendr_rng() {
+    test! {
+        R!(r#"set.seed(20230205)"#).unwrap();
+        let x = generate_big_random_vec();
+        R!(r#"set.seed(20230205)"#).unwrap();
+        let y = generate_big_random_vec();
+        assert_eq!(y, x);
+        // if the rng state isn't retrieved, then distribution is
+        // 0 always
+        assert!(
+            !x.iter().all(|&x| x == 0f64)
+        );
+    }
+}
diff --git a/tests/extendrtests/src/rust/src/altrep.rs b/tests/extendrtests/src/rust/src/altrep.rs
new file mode 100644
index 000000000..4828831ba
--- /dev/null
+++ b/tests/extendrtests/src/rust/src/altrep.rs
@@ -0,0 +1,123 @@
+use extendr_api::prelude::*;
+use extendr_api::AltListImpl;
+
+// struct contains an inner vector of Option<usize> 
+#[derive(Debug, Clone)]
+pub struct VecUsize(pub Vec<Option<usize>>);
+
+impl AltrepImpl for VecUsize {
+    fn length(&self) -> usize {
+        self.0.len()
+    }
+}
+
+// we need to be able to return an Robj of this type so 
+// we add an empty extendr macro above the impl
+#[extendr]
+impl VecUsize {}
+
+impl AltListImpl for VecUsize {
+    fn elt(&self, index: usize) -> Robj {
+        self.into_robj()
+    }
+}
+
+
+#[extendr]
+/// Create an ALTLIST usize vector
+/// 
+/// @param robj an integer vector 
+/// 
+/// The object is `Vec<Option<usize>>` represented as an ALTLIST
+/// @export
+fn new_usize(robj: Integers) -> Altrep {
+    let x = robj
+        .iter()
+        .map(|x| match &x {
+            _ if x.is_na() => None,
+            _ if x.inner() < 0 => None,
+            _ => Some(x.inner() as usize),
+        })
+        .collect();
+    
+    // we can't return the object as is, it needs to
+    // be converted to an altrep object
+    let obj = VecUsize(x);
+    // this provides a hidden class to the altrep object for the package extendrtests
+    let class = Altrep::make_altlist_class::<VecUsize>("li", "mypkg");
+
+    // create an altrep object from the class
+    Altrep::from_state_and_class(obj, class, false)
+}
+
+
+
+
+#[derive(Debug, Clone)]
+struct StringInts {
+    len: usize
+}
+
+impl AltrepImpl for StringInts {
+    fn length(&self) -> usize {
+        self.len as usize
+    }
+}
+
+impl AltStringImpl for StringInts {
+    fn elt(&self, index: usize) -> Rstr {
+        format!("{}", index).into()
+    }
+}
+
+
+#[extendr]
+/// Test ALTSTRING representation
+/// @export
+fn tst_altstring() -> Altrep {
+    let mystate = StringInts { len: 10 };
+    let class = Altrep::make_altstring_class::<StringInts>("si", "mypkg");
+    Altrep::from_state_and_class(mystate, class, false)
+}
+
+
+#[derive(Debug, Clone)]
+struct MyCompactIntRange {
+    start: i32,
+    len: i32,
+    step: i32,
+    missing_index: usize,  // For testing NA
+}
+
+impl AltrepImpl for MyCompactIntRange {
+    fn length(&self) -> usize {
+        self.len as usize
+    }
+}
+
+impl AltIntegerImpl for MyCompactIntRange {
+    fn elt(&self, index: usize) -> Rint {
+        if index == self.missing_index {
+            Rint::na()
+        } else {
+            Rint::new(self.start + self.step * index as i32)
+        }
+    }
+}
+
+
+#[extendr]
+/// Test ALTINTEGER support
+/// @export
+fn tst_altinteger() -> Altrep {
+    let mystate = MyCompactIntRange { start: 0, len: 10, step: 1, missing_index: usize::MAX };
+    let class = Altrep::make_altinteger_class::<MyCompactIntRange>("cir", "mypkg");
+    Altrep::from_state_and_class(mystate, class.clone(), false)
+}
+
+extendr_module! {
+    mod altlist;
+    fn new_usize;
+    fn tst_altstring;
+    fn tst_altinteger;
+}
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 07b3ae413..4aa049ab7 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -12,6 +12,8 @@ mod raw_identifiers;
 
 mod memory_leaks;
 
+// mod altrep;
+
 // Return string `"Hello world!"` to R.
 #[extendr]
 fn hello_world() -> &'static str {
@@ -363,4 +365,7 @@ extendr_module! {
     use optional_either;
     use raw_identifiers;
     use memory_leaks;
+    // use altrep;
 }
+
+
diff --git a/tests/extendrtests/tests/testthat/test-altlist.R b/tests/extendrtests/tests/testthat/test-altlist.R
new file mode 100644
index 000000000..b2354f13c
--- /dev/null
+++ b/tests/extendrtests/tests/testthat/test-altlist.R
@@ -0,0 +1,27 @@
+# Results in a panic!
+# thread '<unnamed>' panicked at .../extendr/extendr-api/src/wrapper/altrep.rs:544:13:
+#   misaligned pointer dereference: address must be a multiple of 0x8 but is 0x1f100000d
+# note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+# thread caused non-unwinding panic. aborting.
+
+# test_that("ALTLIST creation works", {
+#   x <- new_usize(c(1L, NA, 99L))
+#   expect_true(is.list(x))
+#   expect_length(x, 3)
+# })
+# 
+
+# Results in the same panic 
+# test_that("ALTINTEGER creation works", {
+#   x <- tst_altinteger()
+#   expect_true(is.integer(x))
+#   expect_length(x, 10) 
+# })
+# 
+
+# Results in a memory leak test failure
+# test_that("ALTSTRING creation works", {
+#   x <- tst_altstring()
+#   expect_true(is.character(x))
+#   expect_length(x, 10)
+# })