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