Skip to main content

extendr_api/robj/
into_robj.rs

1use super::*;
2use crate::single_threaded;
3use extendr_ffi::{
4    cetype_t, R_BlankString, R_NaInt, R_NaReal, R_NaString, R_NilValue, Rcomplex, Rf_mkCharLenCE,
5    COMPLEX, INTEGER, LOGICAL, RAW, REAL, SET_STRING_ELT, SEXPTYPE,
6};
7mod repeat_into_robj;
8
9/// Returns an `CHARSXP` based on the provided `&str`.
10///
11/// Note that R does string interning, thus repeated application of this
12/// function on the same string, will incur little computational cost.
13///
14/// Note, that you must protect the return value somehow.
15pub(crate) fn str_to_character(s: &str) -> SEXP {
16    unsafe {
17        if s.is_na() {
18            R_NaString
19        } else if s.is_empty() {
20            R_BlankString
21        } else {
22            single_threaded(|| {
23                // this function embeds a terminating \nul
24                Rf_mkCharLenCE(s.as_ptr().cast(), s.len() as i32, cetype_t::CE_UTF8)
25            })
26        }
27    }
28}
29
30/// Convert a null to an Robj.
31impl From<()> for Robj {
32    fn from(_: ()) -> Self {
33        // Note: we do not need to protect this.
34        unsafe { Robj::from_sexp(R_NilValue) }
35    }
36}
37
38/// Convert a [`Result`] to an [`Robj`].
39///
40/// To use the `?`-operator, an extendr-function must return either [`extendr_api::error::Result`] or [`std::result::Result`].
41/// Use of `panic!` in extendr is discouraged due to memory leakage.
42///
43/// Alternative behaviors enabled by feature toggles:
44/// extendr-api supports different conversions from [`Result<T,E>`] into `Robj`.
45/// Below, `x_ok` represents an R variable on R side which was returned from rust via `T::into_robj()` or similar.
46/// Likewise, `x_err` was returned to R side from rust via `E::into_robj()` or similar.
47/// extendr-api
48/// * `result_list`: `Ok(T)` is encoded as `list(ok = x_ok, err = NULL)` and `Err` as `list(ok = NULL, err = e_err)`.
49/// * `result_condition'`: `Ok(T)` is encoded as `x_ok` and `Err(E)` as `condition(msg="extendr_error", value = x_err, class=c("extendr_error", "error", "condition"))`
50/// * More than one enabled feature: Only one feature gate will take effect, the current order of precedence is [`result_list`, `result_condition`, ... ].
51/// * Neither of the above (default): `Ok(T)` is encoded as `x_ok` and `Err(E)` will trigger `throw_r_error()` with the error message.
52/// ```
53/// use extendr_api::prelude::*;
54/// fn my_func() -> extendr_api::error::Result<f64> {
55///     Ok(1.0)
56/// }
57///
58/// test! {
59///     assert_eq!(r!(my_func()), r!(1.0));
60/// }
61/// ```
62///
63/// [`extendr_api::error::Result`]: crate::error::Result
64#[cfg(not(any(feature = "result_list", feature = "result_condition")))]
65impl<T, E> From<std::result::Result<T, E>> for Robj
66where
67    T: Into<Robj>,
68    E: std::fmt::Debug + std::fmt::Display,
69{
70    fn from(res: std::result::Result<T, E>) -> Self {
71        match res {
72            Ok(val) => val.into(),
73            Err(err) => panic!("{}", err),
74        }
75    }
76}
77
78/// Convert a [`Result`] to an [`Robj`]. Return either `Ok` value or `Err` value wrapped in an
79/// error condition. This allows using `?` operator in functions
80/// and returning [`Result<T>`] without panicking on `Err`. `T` must implement [`IntoRobj`].
81///
82/// Returns `Ok` value as is. Returns `Err` wrapped in an R error condition. The `Err` is placed in
83/// $value field of the condition, and its message is set to 'extendr_err'
84#[cfg(all(feature = "result_condition", not(feature = "result_list")))]
85impl<T, E> From<std::result::Result<T, E>> for Robj
86where
87    T: Into<Robj>,
88    E: Into<Robj>,
89{
90    fn from(res: std::result::Result<T, E>) -> Self {
91        use crate as extendr_api;
92        match res {
93            Ok(x) => x.into(),
94            Err(x) => {
95                let mut err = list!(message = "extendr_err", value = x.into());
96                err.set_class(["extendr_error", "error", "condition"])
97                    .expect("internal error: failed to set class");
98                err.into()
99            }
100        }
101    }
102}
103
104/// Convert a `Result` to an R `List` with an `ok` and `err` elements.
105/// This allows using `?` operator in functions
106/// and returning [`std::result::Result`] or [`extendr_api::error::Result`]
107/// without panicking on `Err`.
108///
109/// [`extendr_api::error::Result`]: crate::error::Result
110#[cfg(feature = "result_list")]
111impl<T, E> From<std::result::Result<T, E>> for Robj
112where
113    T: Into<Robj>,
114    E: Into<Robj>,
115{
116    fn from(res: std::result::Result<T, E>) -> Self {
117        use crate as extendr_api;
118        let mut result = match res {
119            Ok(x) => list!(ok = x.into(), err = NULL),
120            Err(x) => {
121                let err_robj = x.into();
122                if err_robj.is_null() {
123                    panic!("Internal error: result_list not allowed to return NULL as err-value")
124                }
125                list!(ok = NULL, err = err_robj)
126            }
127        };
128        result
129            .set_class(&["extendr_result"])
130            .expect("Internal error: failed to set class");
131        result.into()
132    }
133}
134
135// string conversions from Error trait to Robj and String
136impl From<Error> for Robj {
137    fn from(res: Error) -> Self {
138        res.to_string().into()
139    }
140}
141impl From<Error> for String {
142    fn from(res: Error) -> Self {
143        res.to_string()
144    }
145}
146
147/// Convert an Robj reference into a borrowed Robj.
148impl From<&Robj> for Robj {
149    // Note: we should probably have a much better reference
150    // mechanism as double-free or underprotection is a distinct possibility.
151    fn from(val: &Robj) -> Self {
152        unsafe { Robj::from_sexp(val.get()) }
153    }
154}
155
156/// This is an extension trait to provide a convenience method `into_robj()`.
157///
158/// Defer to `From<T> for Robj`-impls if you have custom types.
159///
160pub trait IntoRobj {
161    fn into_robj(self) -> Robj;
162}
163
164impl<T> IntoRobj for T
165where
166    Robj: From<T>,
167{
168    fn into_robj(self) -> Robj {
169        self.into()
170    }
171}
172
173/// `ToVectorValue` is a trait that allows many different types
174/// to be converted to vectors. It is used as a type parameter
175/// to `collect_robj()`.
176pub trait ToVectorValue {
177    fn sexptype() -> SEXPTYPE {
178        SEXPTYPE::NILSXP
179    }
180
181    fn to_real(&self) -> f64
182    where
183        Self: Sized,
184    {
185        0.
186    }
187
188    fn to_complex(&self) -> Rcomplex
189    where
190        Self: Sized,
191    {
192        Rcomplex { r: 0., i: 0. }
193    }
194
195    fn to_integer(&self) -> i32
196    where
197        Self: Sized,
198    {
199        i32::MIN
200    }
201
202    fn to_logical(&self) -> i32
203    where
204        Self: Sized,
205    {
206        i32::MIN
207    }
208
209    fn to_raw(&self) -> u8
210    where
211        Self: Sized,
212    {
213        0
214    }
215
216    fn to_sexp(&self) -> SEXP
217    where
218        Self: Sized,
219    {
220        unsafe { R_NilValue }
221    }
222}
223
224macro_rules! impl_real_tvv {
225    ($t: ty) => {
226        impl ToVectorValue for $t {
227            fn sexptype() -> SEXPTYPE {
228                SEXPTYPE::REALSXP
229            }
230
231            fn to_real(&self) -> f64 {
232                *self as f64
233            }
234        }
235
236        impl ToVectorValue for &$t {
237            fn sexptype() -> SEXPTYPE {
238                SEXPTYPE::REALSXP
239            }
240
241            fn to_real(&self) -> f64 {
242                **self as f64
243            }
244        }
245
246        impl ToVectorValue for Option<$t> {
247            fn sexptype() -> SEXPTYPE {
248                SEXPTYPE::REALSXP
249            }
250
251            fn to_real(&self) -> f64 {
252                if self.is_some() {
253                    self.unwrap() as f64
254                } else {
255                    unsafe { R_NaReal }
256                }
257            }
258        }
259    };
260}
261
262impl_real_tvv!(f64);
263impl_real_tvv!(f32);
264
265// Since these types might exceeds the max or min of R's 32bit integer, we need
266// to return as REALSXP
267impl_real_tvv!(i64);
268impl_real_tvv!(u32);
269impl_real_tvv!(u64);
270impl_real_tvv!(usize);
271
272macro_rules! impl_complex_tvv {
273    ($t: ty) => {
274        impl ToVectorValue for $t {
275            fn sexptype() -> SEXPTYPE {
276                SEXPTYPE::CPLXSXP
277            }
278
279            fn to_complex(&self) -> Rcomplex {
280                unsafe { std::mem::transmute(*self) }
281            }
282        }
283
284        impl ToVectorValue for &$t {
285            fn sexptype() -> SEXPTYPE {
286                SEXPTYPE::CPLXSXP
287            }
288
289            fn to_complex(&self) -> Rcomplex {
290                unsafe { std::mem::transmute(**self) }
291            }
292        }
293    };
294}
295
296impl_complex_tvv!(c64);
297impl_complex_tvv!(Rcplx);
298impl_complex_tvv!((f64, f64));
299
300macro_rules! impl_integer_tvv {
301    ($t: ty) => {
302        impl ToVectorValue for $t {
303            fn sexptype() -> SEXPTYPE {
304                SEXPTYPE::INTSXP
305            }
306
307            fn to_integer(&self) -> i32 {
308                *self as i32
309            }
310        }
311
312        impl ToVectorValue for &$t {
313            fn sexptype() -> SEXPTYPE {
314                SEXPTYPE::INTSXP
315            }
316
317            fn to_integer(&self) -> i32 {
318                **self as i32
319            }
320        }
321
322        impl ToVectorValue for Option<$t> {
323            fn sexptype() -> SEXPTYPE {
324                SEXPTYPE::INTSXP
325            }
326
327            fn to_integer(&self) -> i32 {
328                if self.is_some() {
329                    self.unwrap() as i32
330                } else {
331                    unsafe { R_NaInt }
332                }
333            }
334        }
335    };
336}
337
338impl_integer_tvv!(i8);
339impl_integer_tvv!(i16);
340impl_integer_tvv!(i32);
341impl_integer_tvv!(u16);
342
343impl ToVectorValue for u8 {
344    fn sexptype() -> SEXPTYPE {
345        SEXPTYPE::RAWSXP
346    }
347
348    fn to_raw(&self) -> u8 {
349        *self
350    }
351}
352
353impl ToVectorValue for &u8 {
354    fn sexptype() -> SEXPTYPE {
355        SEXPTYPE::RAWSXP
356    }
357
358    fn to_raw(&self) -> u8 {
359        **self
360    }
361}
362
363macro_rules! impl_str_tvv {
364    ($t: ty) => {
365        impl ToVectorValue for $t {
366            fn sexptype() -> SEXPTYPE {
367                SEXPTYPE::STRSXP
368            }
369
370            fn to_sexp(&self) -> SEXP
371            where
372                Self: Sized,
373            {
374                str_to_character(self.as_ref())
375            }
376        }
377
378        impl ToVectorValue for &$t {
379            fn sexptype() -> SEXPTYPE {
380                SEXPTYPE::STRSXP
381            }
382
383            fn to_sexp(&self) -> SEXP
384            where
385                Self: Sized,
386            {
387                str_to_character(self.as_ref())
388            }
389        }
390
391        impl ToVectorValue for Option<$t> {
392            fn sexptype() -> SEXPTYPE {
393                SEXPTYPE::STRSXP
394            }
395
396            fn to_sexp(&self) -> SEXP
397            where
398                Self: Sized,
399            {
400                if let Some(s) = self {
401                    str_to_character(s.as_ref())
402                } else {
403                    unsafe { R_NaString }
404                }
405            }
406        }
407    };
408}
409
410impl_str_tvv! {&str}
411impl_str_tvv! {String}
412
413impl ToVectorValue for Rstr {
414    fn sexptype() -> SEXPTYPE {
415        SEXPTYPE::STRSXP
416    }
417
418    fn to_sexp(&self) -> SEXP
419    where
420        Self: Sized,
421    {
422        unsafe { self.get() }
423    }
424}
425
426impl ToVectorValue for &Rstr {
427    fn sexptype() -> SEXPTYPE {
428        SEXPTYPE::STRSXP
429    }
430
431    fn to_sexp(&self) -> SEXP
432    where
433        Self: Sized,
434    {
435        unsafe { self.get() }
436    }
437}
438
439impl ToVectorValue for Option<Rstr> {
440    fn sexptype() -> SEXPTYPE {
441        SEXPTYPE::STRSXP
442    }
443
444    fn to_sexp(&self) -> SEXP
445    where
446        Self: Sized,
447    {
448        if let Some(s) = self {
449            unsafe { s.get() }
450        } else {
451            unsafe { R_NaString }
452        }
453    }
454}
455
456impl TryFrom<&Robj> for Rstr {
457    type Error = crate::Error;
458
459    fn try_from(robj: &Robj) -> Result<Self> {
460        let sexptype = robj.sexptype();
461        if let SEXPTYPE::STRSXP = sexptype {
462            if robj.len() == 1 {
463                let strs = Strings::try_from(robj)?;
464                Ok(strs.elt(0))
465            } else {
466                Err(Error::ExpectedRstr(robj.clone()))
467            }
468        } else if let SEXPTYPE::CHARSXP = sexptype {
469            Ok(Rstr { robj: robj.clone() })
470        } else {
471            Err(Error::ExpectedRstr(robj.clone()))
472        }
473    }
474}
475
476impl TryFrom<Robj> for Rstr {
477    type Error = crate::Error;
478
479    fn try_from(value: Robj) -> std::result::Result<Self, Self::Error> {
480        Self::try_from(&value)
481    }
482}
483
484impl GetSexp for Rstr {
485    unsafe fn get(&self) -> SEXP {
486        self.robj.get()
487    }
488
489    unsafe fn get_mut(&mut self) -> SEXP {
490        self.robj.get_mut()
491    }
492
493    fn as_robj(&self) -> &Robj {
494        &self.robj
495    }
496
497    fn as_robj_mut(&mut self) -> &mut Robj {
498        &mut self.robj
499    }
500}
501
502// These traits all derive from GetSexp with default implementations
503impl Length for Rstr {}
504impl Types for Rstr {}
505impl Conversions for Rstr {}
506impl Rinternals for Rstr {}
507impl Slices for Rstr {}
508impl Operators for Rstr {}
509
510impl ToVectorValue for bool {
511    fn sexptype() -> SEXPTYPE {
512        SEXPTYPE::LGLSXP
513    }
514
515    fn to_logical(&self) -> i32
516    where
517        Self: Sized,
518    {
519        *self as i32
520    }
521}
522
523impl ToVectorValue for &bool {
524    fn sexptype() -> SEXPTYPE {
525        SEXPTYPE::LGLSXP
526    }
527
528    fn to_logical(&self) -> i32
529    where
530        Self: Sized,
531    {
532        **self as i32
533    }
534}
535
536impl ToVectorValue for Rbool {
537    fn sexptype() -> SEXPTYPE {
538        SEXPTYPE::LGLSXP
539    }
540
541    fn to_logical(&self) -> i32
542    where
543        Self: Sized,
544    {
545        self.0
546    }
547}
548
549impl ToVectorValue for &Rbool {
550    fn sexptype() -> SEXPTYPE {
551        SEXPTYPE::LGLSXP
552    }
553
554    fn to_logical(&self) -> i32
555    where
556        Self: Sized,
557    {
558        self.0
559    }
560}
561
562impl ToVectorValue for Option<bool> {
563    fn sexptype() -> SEXPTYPE {
564        SEXPTYPE::LGLSXP
565    }
566
567    fn to_logical(&self) -> i32 {
568        if self.is_some() {
569            self.unwrap() as i32
570        } else {
571            unsafe { R_NaInt }
572        }
573    }
574}
575
576impl<T> From<&Option<T>> for Robj
577where
578    Option<T>: ToVectorValue + Clone,
579{
580    fn from(value: &Option<T>) -> Self {
581        value.clone().into()
582    }
583}
584
585// Not thread safe.
586fn fixed_size_collect<I>(iter: I, len: usize) -> Robj
587where
588    I: Iterator,
589    I: Sized,
590    I::Item: ToVectorValue,
591{
592    single_threaded(|| unsafe {
593        // Length of the vector is known in advance.
594        let sexptype = I::Item::sexptype();
595        if sexptype != SEXPTYPE::NILSXP {
596            let res = Robj::alloc_vector(sexptype, len);
597            let sexp = res.get();
598            match sexptype {
599                SEXPTYPE::REALSXP => {
600                    let ptr = REAL(sexp);
601                    for (i, v) in iter.enumerate() {
602                        *ptr.add(i) = v.to_real();
603                    }
604                }
605                SEXPTYPE::CPLXSXP => {
606                    let ptr = COMPLEX(sexp);
607                    for (i, v) in iter.enumerate() {
608                        *ptr.add(i) = v.to_complex();
609                    }
610                }
611                SEXPTYPE::INTSXP => {
612                    let ptr = INTEGER(sexp);
613                    for (i, v) in iter.enumerate() {
614                        *ptr.add(i) = v.to_integer();
615                    }
616                }
617                SEXPTYPE::LGLSXP => {
618                    let ptr = LOGICAL(sexp);
619                    for (i, v) in iter.enumerate() {
620                        *ptr.add(i) = v.to_logical();
621                    }
622                }
623                SEXPTYPE::STRSXP => {
624                    for (i, v) in iter.enumerate() {
625                        SET_STRING_ELT(sexp, i as isize, v.to_sexp());
626                    }
627                }
628                SEXPTYPE::RAWSXP => {
629                    let ptr = RAW(sexp);
630                    for (i, v) in iter.enumerate() {
631                        *ptr.add(i) = v.to_raw();
632                    }
633                }
634                _ => {
635                    panic!("unexpected SEXPTYPE in collect_robj");
636                }
637            }
638            res
639        } else {
640            Robj::from(())
641        }
642    })
643}
644
645/// Extensions to iterators for R objects including [RobjItertools::collect_robj()].
646pub trait RobjItertools: Iterator {
647    /// Convert a wide range of iterators to Robj.
648    /// ```
649    /// use extendr_api::prelude::*;
650    ///
651    /// test! {
652    /// // Integer iterators.
653    /// let robj = (0..3).collect_robj();
654    /// assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
655    ///
656    /// // Logical iterators.
657    /// let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
658    /// assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, TRUE]);
659    ///
660    /// // Numeric iterators.
661    /// let robj = (0..3).map(|x| x as f64).collect_robj();
662    /// assert_eq!(robj.as_real_vector().unwrap(), vec![0., 1., 2.]);
663    ///
664    /// // String iterators.
665    /// let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
666    /// assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
667    /// }
668    /// ```
669    fn collect_robj(self) -> Robj
670    where
671        Self: Iterator,
672        Self: Sized,
673        Self::Item: ToVectorValue,
674    {
675        if let (len, Some(max)) = self.size_hint() {
676            if len == max {
677                return fixed_size_collect(self, len);
678            }
679        }
680        // If the size is indeterminate, create a vector and call recursively.
681        let vec: Vec<_> = self.collect();
682        assert!(vec.iter().size_hint() == (vec.len(), Some(vec.len())));
683        vec.into_iter().collect_robj()
684    }
685
686    /// Collects an iterable into an [`RArray`].
687    /// The iterable must yield items column by column (aka Fortan order)
688    ///
689    /// # Arguments
690    ///
691    /// * `dims` - an array containing the length of each dimension
692    fn collect_rarray<const LEN: usize>(self, dims: [usize; LEN]) -> Result<RArray<Self::Item, LEN>>
693    where
694        Self: Iterator,
695        Self: Sized,
696        Self::Item: ToVectorValue,
697        Robj: for<'a> AsTypedSlice<'a, Self::Item>,
698    {
699        let mut vector = self.collect_robj();
700        let prod = dims.iter().product::<usize>();
701        if prod != vector.len() {
702            return Err(Error::Other(format!(
703                "The vector length ({}) does not match the length implied by the dimensions ({})",
704                vector.len(),
705                prod
706            )));
707        }
708        vector.set_attrib(wrapper::symbol::dim_symbol(), dims.iter().collect_robj())?;
709        let _data = vector.as_typed_slice().ok_or(Error::Other(
710            "Unknown error in converting to slice".to_string(),
711        ))?;
712        Ok(RArray::from_parts(vector))
713    }
714}
715
716// Thanks to *pretzelhammer* on stackoverflow for this.
717impl<T> RobjItertools for T where T: Iterator {}
718
719// Scalars which are ToVectorValue
720impl<T> From<T> for Robj
721where
722    T: ToVectorValue,
723{
724    fn from(scalar: T) -> Self {
725        Some(scalar).into_iter().collect_robj()
726    }
727}
728
729macro_rules! impl_from_as_iterator {
730    ($t: ty) => {
731        impl<T> From<$t> for Robj
732        where
733            $t: RobjItertools,
734            <$t as Iterator>::Item: ToVectorValue,
735            T: ToVectorValue,
736        {
737            fn from(val: $t) -> Self {
738                val.collect_robj()
739            }
740        }
741    };
742}
743
744// impl<T> From<Range<T>> for Robj
745// where
746//     Range<T> : RobjItertools,
747//     <Range<T> as Iterator>::Item: ToVectorValue,
748//     T : ToVectorValue
749// {
750//     fn from(val: Range<T>) -> Self {
751//         val.collect_robj()
752//     }
753// } //
754
755impl<T, const N: usize> From<[T; N]> for Robj
756where
757    T: ToVectorValue,
758{
759    fn from(val: [T; N]) -> Self {
760        fixed_size_collect(val.into_iter(), N)
761    }
762}
763
764impl<'a, T, const N: usize> From<&'a [T; N]> for Robj
765where
766    Self: 'a,
767    &'a T: ToVectorValue + 'a,
768{
769    fn from(val: &'a [T; N]) -> Self {
770        fixed_size_collect(val.iter(), N)
771    }
772}
773
774impl<'a, T, const N: usize> From<&'a mut [T; N]> for Robj
775where
776    Self: 'a,
777    &'a mut T: ToVectorValue + 'a,
778{
779    fn from(val: &'a mut [T; N]) -> Self {
780        fixed_size_collect(val.iter_mut(), N)
781    }
782}
783
784impl<T: ToVectorValue + Clone> From<&Vec<T>> for Robj {
785    fn from(value: &Vec<T>) -> Self {
786        let len = value.len();
787        fixed_size_collect(value.iter().cloned(), len)
788    }
789}
790
791impl<T: ToVectorValue> From<Vec<T>> for Robj {
792    fn from(value: Vec<T>) -> Self {
793        let len = value.len();
794        fixed_size_collect(value.into_iter(), len)
795    }
796}
797
798impl<'a, T> From<&'a [T]> for Robj
799where
800    Self: 'a,
801    T: 'a,
802    &'a T: ToVectorValue,
803{
804    fn from(val: &'a [T]) -> Self {
805        val.iter().collect_robj()
806    }
807}
808
809impl_from_as_iterator! {Range<T>}
810impl_from_as_iterator! {RangeInclusive<T>}
811
812impl From<Vec<Robj>> for Robj {
813    /// Convert a vector of Robj into a list.
814    fn from(val: Vec<Robj>) -> Self {
815        Self::from(&val)
816    }
817}
818
819impl From<&Vec<Robj>> for Robj {
820    fn from(val: &Vec<Robj>) -> Self {
821        List::from_values(val.iter()).into()
822    }
823}
824
825#[cfg(test)]
826mod test {
827    use super::*;
828    use crate as extendr_api;
829
830    #[test]
831    fn test_vec_rint_to_robj() {
832        test! {
833            let int_vec = vec![3,4,0,-2];
834            let int_vec_robj: Robj = int_vec.clone().into();
835            // unsafe { extendr_ffi::Rf_PrintValue(int_vec_robj.get())}
836            assert_eq!(int_vec_robj.as_integer_slice().unwrap(), &int_vec);
837
838            let rint_vec = vec![Rint::from(3), Rint::from(4), Rint::from(0), Rint::from(-2)];
839            let rint_vec_robj: Robj = rint_vec.into();
840            // unsafe { extendr_ffi::Rf_PrintValue(rint_vec_robj.get())}
841            assert_eq!(rint_vec_robj.as_integer_slice().unwrap(), &int_vec);
842        }
843    }
844
845    #[test]
846    fn test_collect_rarray_matrix() {
847        test! {
848            // Check that collect_rarray works the same as R's matrix() function
849            let rmat = (1i32..=16).collect_rarray([4, 4]);
850            assert!(rmat.is_ok());
851            assert_eq!(Robj::from(rmat), R!("matrix(1:16, nrow=4)").unwrap());
852        }
853    }
854
855    #[test]
856    fn test_collect_rarray_tensor() {
857        test! {
858            // Check that collect_rarray works the same as R's array() function
859            let rmat = (1i32..=16).collect_rarray([2, 4, 2]);
860            assert!(rmat.is_ok());
861            assert_eq!(Robj::from(rmat), R!("array(1:16, dim=c(2, 4, 2))").unwrap());
862        }
863    }
864
865    #[test]
866    fn test_collect_rarray_matrix_failure() {
867        test! {
868            // Check that collect_rarray fails when given an invalid shape
869            let rmat = (1i32..=16).collect_rarray([3, 3]);
870            assert!(rmat.is_err());
871            let msg = rmat.unwrap_err().to_string();
872            assert!(msg.contains('9'));
873            assert!(msg.contains("dimension"));
874        }
875    }
876
877    #[test]
878    fn test_collect_tensor_failure() {
879        test! {
880            // Check that collect_rarray fails when given an invalid shape
881            let rmat = (1i32..=16).collect_rarray([3, 3, 3]);
882            assert!(rmat.is_err());
883            let msg = rmat.unwrap_err().to_string();
884            assert!(msg.contains("27"));
885            assert!(msg.contains("dimension"));
886        }
887    }
888
889    #[test]
890    #[cfg(all(feature = "result_condition", not(feature = "result_list")))]
891    fn test_result_condition() {
892        use crate::prelude::*;
893        fn my_err_f() -> std::result::Result<f64, f64> {
894            Err(42.0) // return err float
895        }
896
897        test! {
898                  assert_eq!(
899                    r!(my_err_f()),
900                    R!(
901        "structure(list(message = 'extendr_err',
902        value = 42.0), class = c('extendr_error', 'error', 'condition'))"
903                    ).unwrap()
904                );
905            }
906    }
907
908    #[test]
909    #[cfg(feature = "result_list")]
910    fn test_result_list() {
911        use crate::prelude::*;
912        fn my_err_f() -> std::result::Result<f64, String> {
913            Err("We have water in the engine room!".to_string())
914        }
915
916        fn my_ok_f() -> std::result::Result<f64, String> {
917            Ok(123.123)
918        }
919
920        test! {
921            assert_eq!(
922                r!(my_err_f()),
923                R!("x=list(ok=NULL, err='We have water in the engine room!')
924                    class(x)='extendr_result'
925                    x"
926                ).unwrap()
927            );
928            assert_eq!(
929                r!(my_ok_f()),
930                R!("x = list(ok=123.123, err=NULL)
931                    class(x)='extendr_result'
932                    x"
933                ).unwrap()
934            );
935        }
936    }
937}