Repository: extendr/extendr
Pull request #879 (2025-02-16 19:12:29)

Chore/add from iterator implementations
Housekeeping: add missing `FromIterator` implementation for vector types.
Allows for, e.g.,
```rust
let ints : Integers = (0..3).collect();
```

JosiahParry: Failures are expected on r-devel until https://bugs.r-project.org/show_bug.cgi?id=18806 is addressed or we suppress the `Downloading {crate name}` message

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e3aa7545..475133585 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@
 - Conversion of a `Robj` that contains a `list()`/`List` to a compatible tuple `(T0, ..., Tn)`, where `n` is atmost 12 [[#857]](https://github.com/extendr/extendr/pull/857)
 - Added conversions of `[T;N]` where `T` is `Rint`, `Rfloat`, `Rbool`, `Rcplx`, `u8`,
   `i32`, and `f64`. [[#856]](https://github.com/extendr/extendr/pull/856)
+- `FromIterator<T>` and `FromIterator<&T>` for vector wrapper where `T` represents a matching underlying type (`bool`,
+  `i32`, `f64`, `c64`) [[#879]](https://github.com/extendr/extendr/pull/879)
 
 ### Changed
 
diff --git a/extendr-api/src/wrapper/complexes.rs b/extendr-api/src/wrapper/complexes.rs
index 6a401e6cf..448ddc726 100644
--- a/extendr-api/src/wrapper/complexes.rs
+++ b/extendr-api/src/wrapper/complexes.rs
@@ -8,7 +8,7 @@ use std::iter::FromIterator;
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let mut vec = (0..5).map(|i| (i as f64).into()).collect::<Complexes>();
+///     let mut vec = (0..5).map(|i| c64::from(i as f64)).collect::<Complexes>();
 ///     assert_eq!(vec.len(), 5);
 /// }
 /// ```  
@@ -18,7 +18,7 @@ pub struct Complexes {
 }
 
 use libR_sys::SEXPTYPE::CPLXSXP;
-crate::wrapper::macros::gen_vector_wrapper_impl!(
+macros::gen_vector_wrapper_impl!(
     vector_type: Complexes,
     scalar_type: Rcplx,
     primitive_type: c64,
@@ -28,6 +28,14 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altcomplex_from_iterator,
 );
 
+macros::gen_from_iterator_impl!(
+    vector_type: Complexes,
+    collect_from_type: c64,
+    underlying_type: Rcplx,
+    SEXP: CPLXSXP,
+    assignment: |dest: &mut Rcplx, val: c64| *dest = val.into()
+);
+
 impl Complexes {
     /// Get a region of elements from the vector.
     pub fn get_region(&self, index: usize, dest: &mut [Rcplx]) -> usize {
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
index fe3b49fec..b9cb106f1 100644
--- a/extendr-api/src/wrapper/doubles.rs
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -8,7 +8,7 @@ use std::iter::FromIterator;
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let mut vec = (0..5).map(|i| (i as f64).into()).collect::<Doubles>();
+///     let mut vec = (0..5).map(|i| (i as f64)).collect::<Doubles>();
 ///     vec.iter_mut().for_each(|v| *v = *v + 10.0);
 ///     assert_eq!(vec.elt(0), 10.0);
 ///     let sum = vec.iter().sum::<Rfloat>();
@@ -21,7 +21,7 @@ pub struct Doubles {
 }
 
 use libR_sys::SEXPTYPE::REALSXP;
-crate::wrapper::macros::gen_vector_wrapper_impl!(
+macros::gen_vector_wrapper_impl!(
     vector_type: Doubles,
     scalar_type: Rfloat,
     primitive_type: f64,
@@ -31,6 +31,14 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altreal_from_iterator,
 );
 
+macros::gen_from_iterator_impl!(
+    vector_type: Doubles,
+    collect_from_type: f64,
+    underlying_type: f64,
+    SEXP: REALSXP,
+    assignment: |dest: &mut f64, val: f64| *dest = val
+);
+
 impl Doubles {
     /// Get a region of elements from the vector.
     pub fn get_region(&self, index: usize, dest: &mut [Rfloat]) -> usize {
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index 280c81da7..f9cce8352 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -8,7 +8,7 @@ use std::iter::FromIterator;
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let mut vec = (0..5).map(|i| i.into()).collect::<Integers>();
+///     let mut vec = (0..5).collect::<Integers>();
 ///     vec.iter_mut().for_each(|v| *v = *v + 10);
 ///     assert_eq!(vec.elt(0), 10);
 ///     let sum = vec.iter().sum::<Rint>();
@@ -21,7 +21,7 @@ pub struct Integers {
 }
 
 use libR_sys::SEXPTYPE::INTSXP;
-crate::wrapper::macros::gen_vector_wrapper_impl!(
+macros::gen_vector_wrapper_impl!(
     vector_type: Integers, // Implements for
     scalar_type: Rint,     // Element type
     primitive_type: i32,   // Raw element type
@@ -31,6 +31,14 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altinteger_from_iterator,
 );
 
+macros::gen_from_iterator_impl!(
+    vector_type: Integers,
+    collect_from_type: i32,
+    underlying_type: i32,
+    SEXP: INTSXP,
+    assignment: |dest: &mut i32, val: i32| *dest = val
+);
+
 impl Integers {
     /// Get a region of elements from the vector.
     pub fn get_region(&self, index: usize, dest: &mut [Rint]) -> usize {
@@ -108,7 +116,7 @@ mod tests {
     #[test]
     fn from_iterator() {
         test! {
-            let vec : Integers = (0..3).map(|i| i.into()).collect();
+            let vec : Integers = (0..3).collect();
             assert_eq!(vec, Integers::from_values([0, 1, 2]));
         }
     }
diff --git a/extendr-api/src/wrapper/logicals.rs b/extendr-api/src/wrapper/logicals.rs
index a224ed37b..8cf361716 100644
--- a/extendr-api/src/wrapper/logicals.rs
+++ b/extendr-api/src/wrapper/logicals.rs
@@ -9,7 +9,7 @@ use std::iter::FromIterator;
 /// use extendr_api::prelude::*;
 /// test! {
 ///     // Collect builds a Logicals from an iterator
-///     let mut vec = (0..5).map(|i| (i % 2 == 0).into()).collect::<Logicals>();
+///     let mut vec = (0..5).map(|i| (i % 2 == 0)).collect::<Logicals>();
 ///     // elt accesses a single element (altrep aware).
 ///     assert_eq!(vec.elt(0), true);
 ///     // Logicals behaves like &[Rbool]
@@ -22,7 +22,7 @@ pub struct Logicals {
 }
 
 use SEXPTYPE::LGLSXP;
-crate::wrapper::macros::gen_vector_wrapper_impl!(
+macros::gen_vector_wrapper_impl!(
     vector_type: Logicals, // Implements for
     scalar_type: Rbool,    // Element type
     primitive_type: i32,   // Raw element type
@@ -32,6 +32,14 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altlogical_from_iterator,
 );
 
+macros::gen_from_iterator_impl!(
+    vector_type: Logicals,
+    collect_from_type: bool,
+    underlying_type: Rbool,
+    SEXP: LGLSXP,
+    assignment: |dest: &mut Rbool, val : bool| *dest = val.into()
+);
+
 impl Logicals {
     /// Get a region of elements from the vector.
     pub fn get_region(&self, index: usize, dest: &mut [Rbool]) -> usize {
@@ -103,11 +111,21 @@ mod tests {
     #[test]
     fn from_iterator() {
         test! {
-            let vec : Logicals = (0..3).map(|i| (i % 2 == 0).into()).collect();
+            let vec : Logicals = (0..3).map(|i| i % 2 == 0).collect();
             assert_eq!(vec, Logicals::from_values([true, false, true]));
         }
     }
 
+    #[test]
+    fn from_iterator_ref() {
+        test! {
+            let src = vec![true, false, true];
+            let iter = src.iter();
+            let vec : Logicals = iter.collect();
+            assert_eq!(vec, Logicals::from_values(src));
+        }
+    }
+
     #[test]
     fn iter_mut() {
         test! {
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index b6c2206e9..4751ac7fe 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -151,4 +151,53 @@ macro_rules! gen_vector_wrapper_impl {
     }
 }
 
+macro_rules! gen_from_iterator_impl {
+    (
+        vector_type: $type : ident,
+        collect_from_type: $collect_from_type : ty,
+        underlying_type: $underlying_type : ty,
+        SEXP: $sexp : ident,
+        assignment: $assignment : expr
+    ) => {
+        impl FromIterator<$collect_from_type> for $type {
+            /// A more generalised iterator collector for small vectors.
+            /// Generates a non-ALTREP vector.
+            fn from_iter<T: IntoIterator<Item = $collect_from_type>>(iter: T) -> Self {
+                // Collect into a vector first.
+                // TODO: specialise for ExactSizeIterator.
+                let values: Vec<$collect_from_type> = iter.into_iter().collect();
+
+                let mut robj = Robj::alloc_vector($sexp, values.len());
+                let dest: &mut [$underlying_type] = robj.as_typed_slice_mut().unwrap();
+
+                for (d, v) in dest.iter_mut().zip(values) {
+                    $assignment(d, v)
+                }
+
+                $type { robj }
+            }
+        }
+
+        impl<'a> FromIterator<&'a $collect_from_type> for $type {
+            /// A more generalised iterator collector for small vectors.
+            /// Generates a non-ALTREP vector.
+            fn from_iter<T: IntoIterator<Item = &'a $collect_from_type>>(iter: T) -> Self {
+                // Collect into a vector first.
+                // TODO: specialise for ExactSizeIterator.
+                let values: Vec<&'a $collect_from_type> = iter.into_iter().collect();
+
+                let mut robj = Robj::alloc_vector($sexp, values.len());
+                let dest: &mut [$underlying_type] = robj.as_typed_slice_mut().unwrap();
+
+                for (d, v) in dest.iter_mut().zip(values) {
+                    $assignment(d, *v)
+                }
+
+                $type { robj }
+            }
+        }
+    };
+}
+
+pub(in crate::wrapper) use gen_from_iterator_impl;
 pub(in crate::wrapper) use gen_vector_wrapper_impl;


Test patch:
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index 7d9d119d3..dcb571527 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -245,7 +245,7 @@ fn test_rstr() {
 #[test]
 fn test_doubles_from_iterator() {
     test! {
-        let vec : Doubles = (0..3).map(|i| (i as f64).into()).collect();
+        let vec : Doubles = (0..3).map(|x| x as f64).collect();
         assert_eq!(vec, Doubles::from_values([0.0, 1.0, 2.0]));
     }
 }
@@ -306,7 +306,7 @@ mod num_complex {
     #[test]
     fn from_iterator() {
         test! {
-            let vec : Complexes = (0..3).map(|i| (i as f64).into()).collect();
+            let vec : Complexes = (0..3).map(|i| c64::from(i as f64)).collect();
             assert_eq!(vec, Complexes::from_values([0.0, 1.0, 2.0]));
         }
     }