Skip to main content

extendr_api/robj/
try_from_robj.rs

1//! There are various ways an [`Robj`] may be converted into different types `T`.
2//!
3//! This module defines these conversions on `&Robj`. Due to internal reference
4//! counting measure of [`ownership`]-module, it is cheaper to copy `&Robj`,
5//! than copying `Robj`, as the latter will incur an increase in reference counting.
6//!
7//!
8//! [`ownership`]: crate::ownership
9use super::*;
10use crate as extendr_api;
11use crate::conversions::try_into_int::FloatToInt;
12
13macro_rules! impl_try_from_scalar_integer {
14    ($t:ty) => {
15        impl TryFrom<&Robj> for $t {
16            type Error = Error;
17
18            /// Convert a numeric object to an integer value.
19            fn try_from(robj: &Robj) -> Result<Self> {
20                // Check if the value is a scalar
21                match robj.len() {
22                    0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
23                    1 => {}
24                    _ => return Err(Error::ExpectedScalar(robj.clone())),
25                };
26
27                // Check if the value is not a missing value
28                if robj.is_na() {
29                    return Err(Error::MustNotBeNA(robj.clone()));
30                }
31
32                // If the conversion is int-to-int, check the limits. This
33                // needs to be done by `TryFrom` because the conversion by `as`
34                // is problematic when converting a negative value to unsigned
35                // integer types (e.g. `-1i32 as u8` becomes 255).
36                if let Some(v) = robj.as_integer() {
37                    return Self::try_from(v).map_err(|_| Error::OutOfLimits(robj.clone()));
38                }
39
40                // If the conversion is float-to-int, check if the value is
41                // integer-like (i.e., an integer, or a float representing a
42                // whole number).
43                if let Some(v) = robj.as_real() {
44                    return v
45                        .try_into_int()
46                        .map_err(|conv_err| Error::ExpectedWholeNumber(robj.clone(), conv_err));
47                }
48
49                Err(Error::ExpectedNumeric(robj.clone()))
50            }
51        }
52    };
53}
54
55macro_rules! impl_try_from_scalar_real {
56    ($t:ty) => {
57        impl TryFrom<&Robj> for $t {
58            type Error = Error;
59
60            /// Convert a numeric object to a real value.
61            fn try_from(robj: &Robj) -> Result<Self> {
62                // Check if the value is a scalar
63                match robj.len() {
64                    0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
65                    1 => {}
66                    _ => return Err(Error::ExpectedScalar(robj.clone())),
67                };
68
69                // Check if the value is not a missing value
70                if robj.is_na() {
71                    return Err(Error::MustNotBeNA(robj.clone()));
72                }
73
74                // `<Robj>::as_xxx()` methods can work only when the underlying
75                // `SEXP` is the corresponding type, so we cannot use `as_real()`
76                // directly on `INTSXP`.
77                if let Some(v) = robj.as_real() {
78                    // f64 to f32 and f64 to f64 is always safe.
79                    return Ok(v as Self);
80                }
81                if let Some(v) = robj.as_integer() {
82                    // An i32 R integer can be represented exactly by f64, but might be truncated in f32.
83                    return Ok(v as Self);
84                }
85
86                Err(Error::ExpectedNumeric(robj.clone()))
87            }
88        }
89    };
90}
91
92macro_rules! impl_typed_slice_conversions {
93    ($type:ty, $error:ident, $desc:expr) => {
94        impl_typed_slice_conversions!($type, $error, $error, $error, $desc);
95    };
96    ($type:ty, $vec_error:ident, $slice_error:ident, $mut_error:ident, $desc:expr) => {
97        impl TryFrom<&Robj> for Vec<$type> {
98            type Error = Error;
99
100            #[doc = concat!("Convert ", $desc, " into `Vec<", stringify!($type), ">`.")]
101            #[doc = "Note: Unless you plan to store the result, use a slice instead."]
102            fn try_from(robj: &Robj) -> Result<Self> {
103                robj.as_typed_slice()
104                    .map(<[_]>::to_vec)
105                    .ok_or_else(|| Error::$vec_error(robj.clone()))
106            }
107        }
108
109        impl TryFrom<&Robj> for &[$type] {
110            type Error = Error;
111
112            #[doc = concat!("Convert ", $desc, " into `&[", stringify!($type), "]`.")]
113            fn try_from(robj: &Robj) -> Result<Self> {
114                robj.as_typed_slice()
115                    .ok_or_else(|| Error::$slice_error(robj.clone()))
116            }
117        }
118
119        impl TryFrom<&mut Robj> for &mut [$type] {
120            type Error = Error;
121
122            #[doc = concat!("Convert ", $desc, " into `&mut [", stringify!($type), "]`.")]
123            fn try_from(robj: &mut Robj) -> Result<Self> {
124                robj.as_typed_slice_mut()
125                    .ok_or_else(|| Error::$mut_error(robj.clone()))
126            }
127        }
128
129        impl TryFrom<&Robj> for Option<&[$type]> {
130            type Error = Error;
131
132            #[doc = concat!("Convert ", $desc, " into `Option<&[", stringify!($type), "]>`.")]
133            fn try_from(robj: &Robj) -> Result<Self> {
134                if robj.is_null() || robj.is_na() {
135                    Ok(None)
136                } else {
137                    Ok(Some(<&[$type]>::try_from(robj)?))
138                }
139            }
140        }
141
142        impl TryFrom<&mut Robj> for Option<&mut [$type]> {
143            type Error = Error;
144
145            #[doc = concat!("Convert ", $desc, " into `Option<&mut [", stringify!($type), "]>`.")]
146            fn try_from(robj: &mut Robj) -> Result<Self> {
147                if robj.is_null() || robj.is_na() {
148                    Ok(None)
149                } else {
150                    Ok(Some(<&mut [$type]>::try_from(robj)?))
151                }
152            }
153        }
154
155        impl TryFrom<&Robj> for &$type {
156            type Error = Error;
157
158            #[doc = concat!("Convert ", $desc, " into `&", stringify!($type), "`.")]
159            fn try_from(robj: &Robj) -> Result<Self> {
160                let slice: &[$type] = robj.try_into()?;
161
162                if slice.is_empty() {
163                    return Err(Error::ExpectedNonZeroLength(robj.clone()));
164                }
165                if slice.len() != 1 {
166                    return Err(Error::ExpectedScalar(robj.clone()));
167                }
168                let Some(value) = slice.get(0) else {
169                    unreachable!()
170                };
171                if value.is_na() {
172                    return Err(Error::MustNotBeNA(robj.clone()));
173                }
174                Ok(value)
175            }
176        }
177
178        impl TryFrom<&mut Robj> for &mut $type {
179            type Error = Error;
180
181            #[doc = concat!("Convert ", $desc, " into `&mut ", stringify!($type), "`.")]
182            fn try_from(robj: &mut Robj) -> Result<Self> {
183                let slice: &mut [$type] = robj.try_into()?;
184
185                if slice.is_empty() {
186                    return Err(Error::ExpectedNonZeroLength(robj.clone()));
187                }
188                if slice.len() != 1 {
189                    return Err(Error::ExpectedScalar(robj.clone()));
190                }
191                let Some(value) = slice.get_mut(0) else {
192                    unreachable!()
193                };
194                if value.is_na() {
195                    return Err(Error::MustNotBeNA(robj.clone()));
196                }
197                Ok(value)
198            }
199        }
200    };
201}
202
203impl_try_from_scalar_integer!(u8);
204impl_try_from_scalar_integer!(u16);
205impl_try_from_scalar_integer!(u32);
206impl_try_from_scalar_integer!(u64);
207impl_try_from_scalar_integer!(usize);
208impl_try_from_scalar_integer!(i8);
209impl_try_from_scalar_integer!(i16);
210impl_try_from_scalar_integer!(i32);
211impl_try_from_scalar_integer!(i64);
212impl_try_from_scalar_integer!(isize);
213impl_try_from_scalar_real!(f32);
214impl_try_from_scalar_real!(f64);
215
216impl TryFrom<&Robj> for bool {
217    type Error = Error;
218
219    /// Convert an LGLSXP object into a boolean.
220    /// NAs are not allowed.
221    fn try_from(robj: &Robj) -> Result<Self> {
222        if robj.is_na() {
223            Err(Error::MustNotBeNA(robj.clone()))
224        } else {
225            Ok(<Rbool>::try_from(robj)?.is_true())
226        }
227    }
228}
229
230impl TryFrom<&Robj> for &str {
231    type Error = Error;
232
233    /// Convert a scalar STRSXP object into a string slice.
234    /// NAs are not allowed.
235    fn try_from(robj: &Robj) -> Result<Self> {
236        if robj.is_na() {
237            return Err(Error::MustNotBeNA(robj.clone()));
238        }
239        match robj.len() {
240            0 => Err(Error::ExpectedNonZeroLength(robj.clone())),
241            1 => {
242                if let Some(s) = robj.as_str() {
243                    Ok(s)
244                } else {
245                    Err(Error::ExpectedString(robj.clone()))
246                }
247            }
248            _ => Err(Error::ExpectedScalar(robj.clone())),
249        }
250    }
251}
252
253impl TryFrom<&Robj> for Option<&str> {
254    type Error = Error;
255
256    fn try_from(robj: &Robj) -> Result<Self> {
257        if robj.is_null() || robj.is_na() {
258            Ok(None)
259        } else {
260            Ok(Some(<&str>::try_from(robj)?))
261        }
262    }
263}
264
265impl TryFrom<&Robj> for String {
266    type Error = Error;
267
268    /// Convert an scalar STRSXP object into a String.
269    /// Note: Unless you plan to store the result, use a string slice instead.
270    /// NAs are not allowed.
271    fn try_from(robj: &Robj) -> Result<Self> {
272        <&str>::try_from(robj).map(|s| s.to_string())
273    }
274}
275
276impl_typed_slice_conversions!(i32, ExpectedInteger, "an INTSXP object");
277impl_typed_slice_conversions!(Rint, ExpectedInteger, "an INTSXP object");
278impl_typed_slice_conversions!(Rfloat, ExpectedReal, "a REALSXP object");
279impl_typed_slice_conversions!(
280    Rbool,
281    ExpectedInteger,
282    ExpectedLogical,
283    ExpectedLogical,
284    "a LGLSXP object"
285);
286impl_typed_slice_conversions!(Rcplx, ExpectedComplex, "a complex object");
287impl_typed_slice_conversions!(u8, ExpectedRaw, "a RAWSXP object");
288impl_typed_slice_conversions!(f64, ExpectedReal, "a REALSXP object");
289
290impl TryFrom<&Robj> for Vec<String> {
291    type Error = Error;
292
293    /// Convert a STRSXP object into a vector of `String`s.
294    /// Note: Unless you plan to store the result, use a slice instead.
295    fn try_from(robj: &Robj) -> Result<Self> {
296        if let Some(iter) = robj.as_str_iter() {
297            // check for NA's in the string vector
298            if iter.clone().any(|s| s.is_na()) {
299                Err(Error::MustNotBeNA(robj.clone()))
300            } else {
301                Ok(iter.map(|s| s.to_string()).collect::<Vec<String>>())
302            }
303        } else {
304            Err(Error::ExpectedString(robj.clone()))
305        }
306    }
307}
308
309impl TryFrom<&Robj> for Rcplx {
310    type Error = Error;
311
312    fn try_from(robj: &Robj) -> Result<Self> {
313        // Check if the value is a scalar
314        match robj.len() {
315            0 => return Err(Error::ExpectedNonZeroLength(robj.clone())),
316            1 => {}
317            _ => return Err(Error::ExpectedScalar(robj.clone())),
318        };
319
320        // Check if the value is not a missing value.
321        if robj.is_na() {
322            return Ok(Rcplx::na());
323        }
324
325        // This should always work, NA is handled above.
326        if let Some(v) = robj.as_real() {
327            return Ok(Rcplx::from(v));
328        }
329
330        // Any integer (32 bit) can be represented as f64,
331        // this always works.
332        if let Some(v) = robj.as_integer() {
333            return Ok(Rcplx::from(v as f64));
334        }
335
336        // Complex slices return their first element.
337        if let Some(s) = robj.as_typed_slice() {
338            return Ok(s[0]);
339        }
340
341        Err(Error::ExpectedComplex(robj.clone()))
342    }
343}
344
345// Convert TryFrom<&Robj> into TryFrom<Robj>. Sadly, we are unable to make a blanket
346// conversion using GetSexp with the current version of Rust.
347macro_rules! impl_try_from_robj {
348    ($(@generics<$generics:tt>)? $type:ty $(where $($where_clause:tt)*)?) => {
349        impl$(<$generics>)? TryFrom<Robj> for $type $(where $($where_clause)*)? {
350            type Error = Error;
351
352            fn try_from(robj: Robj) -> Result<Self> {
353                Self::try_from(&robj)
354            }
355        }
356
357        impl$(<$generics>)? TryFrom<&Robj> for Option<$type> $(where $($where_clause)*)? {
358            type Error = Error;
359
360            fn try_from(robj: &Robj) -> Result<Self> {
361                if robj.is_null() || robj.is_na() {
362                    Ok(None)
363                } else {
364                    Ok(Some(<$type>::try_from(robj)?))
365                }
366            }
367        }
368
369        impl$(<$generics>)? TryFrom<Robj> for Option<$type> $(where $($where_clause)*)? {
370            type Error = Error;
371
372            fn try_from(robj: Robj) -> Result<Self> {
373                Self::try_from(&robj)
374            }
375        }
376    };
377}
378#[rustfmt::skip]
379impl_try_from_robj!(u8);
380impl_try_from_robj!(u16);
381impl_try_from_robj!(u32);
382impl_try_from_robj!(u64);
383impl_try_from_robj!(usize);
384
385impl_try_from_robj!(i8);
386impl_try_from_robj!(i16);
387impl_try_from_robj!(i32);
388impl_try_from_robj!(i64);
389impl_try_from_robj!(isize);
390
391impl_try_from_robj!(bool);
392
393impl_try_from_robj!(Rint);
394impl_try_from_robj!(Rfloat);
395impl_try_from_robj!(Rbool);
396impl_try_from_robj!(Rcplx);
397
398impl_try_from_robj!(f32);
399impl_try_from_robj!(f64);
400
401impl_try_from_robj!(Vec::<String>);
402impl_try_from_robj!(Vec::<Rint>);
403impl_try_from_robj!(Vec::<Rfloat>);
404impl_try_from_robj!(Vec::<Rbool>);
405impl_try_from_robj!(Vec::<Rcplx>);
406impl_try_from_robj!(Vec::<u8>);
407impl_try_from_robj!(Vec::<i32>);
408impl_try_from_robj!(Vec::<f64>);
409
410impl_try_from_robj!(String);
411
412impl TryFrom<&Robj> for Option<Environment> {
413    type Error = Error;
414    fn try_from(robj: &Robj) -> Result<Self> {
415        if robj.is_null() || robj.is_na() {
416            Ok(None)
417        } else {
418            Ok(Some(Environment::try_from(robj)?))
419        }
420    }
421}
422
423impl TryFrom<Robj> for Option<Environment> {
424    type Error = Error;
425    fn try_from(robj: Robj) -> Result<Self> {
426        Self::try_from(&robj)
427    }
428}
429
430impl_try_from_robj!(@generics<T> HashMap::<&str, T> where T: TryFrom<Robj, Error = error::Error>);
431impl_try_from_robj!(@generics<T> HashMap::<String,T> where T: TryFrom<Robj, Error = error::Error>);
432
433impl_try_from_robj!(HashMap::<&str, Robj>);
434impl_try_from_robj!(HashMap::<String, Robj>);
435
436impl TryFrom<&Robj> for Option<()> {
437    type Error = Error;
438
439    fn try_from(value: &Robj) -> Result<Self> {
440        if value.is_null() {
441            Ok(Some(()))
442        } else {
443            Err(Error::ExpectedNull(value.clone()))
444        }
445    }
446}
447
448impl TryFrom<Robj> for Option<()> {
449    type Error = Error;
450    fn try_from(robj: Robj) -> Result<Self> {
451        Self::try_from(&robj)
452    }
453}
454
455impl<T> TryFrom<&Robj> for HashMap<&str, T>
456where
457    T: TryFrom<Robj, Error = error::Error>,
458{
459    type Error = Error;
460
461    fn try_from(value: &Robj) -> Result<Self> {
462        let value: List = value.try_into()?;
463
464        let value = value
465            .iter()
466            .map(|(name, value)| -> Result<(&str, T)> { value.try_into().map(|x| (name, x)) })
467            .collect::<Result<HashMap<_, _>>>()?;
468
469        Ok(value)
470    }
471}
472
473impl<T> TryFrom<&Robj> for HashMap<String, T>
474where
475    T: TryFrom<Robj, Error = error::Error>,
476{
477    type Error = Error;
478    fn try_from(value: &Robj) -> Result<Self> {
479        let value: HashMap<&str, _> = value.try_into()?;
480        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
481    }
482}
483
484macro_rules! impl_try_from_robj_for_arrays {
485    ($slice_type:ty) => {
486        impl<const N: usize> TryFrom<&Robj> for [$slice_type; N] {
487            type Error = Error;
488
489            fn try_from(value: &Robj) -> Result<Self> {
490                let value: &[$slice_type] = value.try_into()?;
491                if value.len() != N {
492                    return Err(Error::ExpectedLength(N));
493                }
494                let value: Self = value
495                    .try_into()
496                    .map_err(|error| format!("{}", error).to_string())?;
497                Ok(value)
498            }
499        }
500
501        // TODO: the following can be integrated into `impl_try_from_robj` later
502
503        impl<const N: usize> TryFrom<Robj> for [$slice_type; N] {
504            type Error = Error;
505
506            fn try_from(robj: Robj) -> Result<Self> {
507                Self::try_from(&robj)
508            }
509        }
510
511        impl<const N: usize> TryFrom<&Robj> for Option<[$slice_type; N]> {
512            type Error = Error;
513
514            fn try_from(robj: &Robj) -> Result<Self> {
515                if robj.is_null() || robj.is_na() {
516                    Ok(None)
517                } else {
518                    Ok(Some(<[$slice_type; N]>::try_from(robj)?))
519                }
520            }
521        }
522
523        impl<const N: usize> TryFrom<Robj> for Option<[$slice_type; N]> {
524            type Error = Error;
525
526            fn try_from(robj: Robj) -> Result<Self> {
527                Self::try_from(&robj)
528            }
529        }
530    };
531}
532
533impl_try_from_robj_for_arrays!(Rint);
534impl_try_from_robj_for_arrays!(Rfloat);
535impl_try_from_robj_for_arrays!(Rbool);
536impl_try_from_robj_for_arrays!(Rcplx);
537impl_try_from_robj_for_arrays!(u8);
538impl_try_from_robj_for_arrays!(i32);
539impl_try_from_robj_for_arrays!(f64);
540
541// Choosing arity 12.. As the Rust compiler did for these [Tuple to array conversion](https://doc.rust-lang.org/stable/std/primitive.tuple.html#trait-implementations-1)
542// Single-element tuple manually implemented to avoid clippy::needless_question_mark
543
544// We implement the 1-length tuple variant manually
545// so that we avoid clippy needless Ok(?) lint
546impl<T0> TryFrom<&Robj> for (T0,)
547where
548    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
549{
550    type Error = Error;
551
552    fn try_from(robj: &Robj) -> Result<Self> {
553        let list: List = robj.try_into()?;
554        if list.len() != 1 {
555            return Err(Error::ExpectedLength(1));
556        }
557        Ok(((&list.elt(0)?).try_into()?,))
558    }
559}
560
561impl<T0> TryFrom<Robj> for (T0,)
562where
563    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
564{
565    type Error = Error;
566
567    fn try_from(robj: Robj) -> Result<Self> {
568        Self::try_from(&robj)
569    }
570}
571
572impl<T0> TryFrom<&Robj> for Option<(T0,)>
573where
574    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
575{
576    type Error = Error;
577
578    fn try_from(robj: &Robj) -> Result<Self> {
579        if robj.is_null() || robj.is_na() {
580            Ok(None)
581        } else {
582            <(T0,)>::try_from(robj).map(Some)
583        }
584    }
585}
586
587impl<T0> TryFrom<Robj> for Option<(T0,)>
588where
589    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
590{
591    type Error = Error;
592
593    fn try_from(robj: Robj) -> Result<Self> {
594        Self::try_from(&robj)
595    }
596}
597
598impl_try_from_robj_tuples!((2, 12));
599
600// The following is necessary because it is impossible to define `TryFrom<Robj> for &Robj` as
601// it requires returning a reference to a owned (moved) value
602impl TryFrom<&Robj> for HashMap<&str, Robj> {
603    type Error = Error;
604
605    fn try_from(value: &Robj) -> Result<Self> {
606        let value: List = value.try_into()?;
607        Ok(value.into_iter().collect())
608    }
609}
610
611impl TryFrom<&Robj> for HashMap<String, Robj> {
612    type Error = Error;
613    fn try_from(value: &Robj) -> Result<Self> {
614        let value: HashMap<&str, _> = value.try_into()?;
615        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
616    }
617}