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_try_from_robj!(@generics<T> HashMap::<&str, T> where T: TryFrom<Robj, Error = error::Error>);
413impl_try_from_robj!(@generics<T> HashMap::<String,T> where T: TryFrom<Robj, Error = error::Error>);
414
415impl_try_from_robj!(HashMap::<&str, Robj>);
416impl_try_from_robj!(HashMap::<String, Robj>);
417
418impl TryFrom<&Robj> for Option<()> {
419    type Error = Error;
420
421    fn try_from(value: &Robj) -> Result<Self> {
422        if value.is_null() {
423            Ok(Some(()))
424        } else {
425            Err(Error::ExpectedNull(value.clone()))
426        }
427    }
428}
429
430impl TryFrom<Robj> for Option<()> {
431    type Error = Error;
432    fn try_from(robj: Robj) -> Result<Self> {
433        Self::try_from(&robj)
434    }
435}
436
437impl<T> TryFrom<&Robj> for HashMap<&str, T>
438where
439    T: TryFrom<Robj, Error = error::Error>,
440{
441    type Error = Error;
442
443    fn try_from(value: &Robj) -> Result<Self> {
444        let value: List = value.try_into()?;
445
446        let value = value
447            .iter()
448            .map(|(name, value)| -> Result<(&str, T)> { value.try_into().map(|x| (name, x)) })
449            .collect::<Result<HashMap<_, _>>>()?;
450
451        Ok(value)
452    }
453}
454
455impl<T> TryFrom<&Robj> for HashMap<String, T>
456where
457    T: TryFrom<Robj, Error = error::Error>,
458{
459    type Error = Error;
460    fn try_from(value: &Robj) -> Result<Self> {
461        let value: HashMap<&str, _> = value.try_into()?;
462        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
463    }
464}
465
466macro_rules! impl_try_from_robj_for_arrays {
467    ($slice_type:ty) => {
468        impl<const N: usize> TryFrom<&Robj> for [$slice_type; N] {
469            type Error = Error;
470
471            fn try_from(value: &Robj) -> Result<Self> {
472                let value: &[$slice_type] = value.try_into()?;
473                if value.len() != N {
474                    return Err(Error::ExpectedLength(N));
475                }
476                let value: Self = value
477                    .try_into()
478                    .map_err(|error| format!("{}", error).to_string())?;
479                Ok(value)
480            }
481        }
482
483        // TODO: the following can be integrated into `impl_try_from_robj` later
484
485        impl<const N: usize> TryFrom<Robj> for [$slice_type; N] {
486            type Error = Error;
487
488            fn try_from(robj: Robj) -> Result<Self> {
489                Self::try_from(&robj)
490            }
491        }
492
493        impl<const N: usize> TryFrom<&Robj> for Option<[$slice_type; N]> {
494            type Error = Error;
495
496            fn try_from(robj: &Robj) -> Result<Self> {
497                if robj.is_null() || robj.is_na() {
498                    Ok(None)
499                } else {
500                    Ok(Some(<[$slice_type; N]>::try_from(robj)?))
501                }
502            }
503        }
504
505        impl<const N: usize> TryFrom<Robj> for Option<[$slice_type; N]> {
506            type Error = Error;
507
508            fn try_from(robj: Robj) -> Result<Self> {
509                Self::try_from(&robj)
510            }
511        }
512    };
513}
514
515impl_try_from_robj_for_arrays!(Rint);
516impl_try_from_robj_for_arrays!(Rfloat);
517impl_try_from_robj_for_arrays!(Rbool);
518impl_try_from_robj_for_arrays!(Rcplx);
519impl_try_from_robj_for_arrays!(u8);
520impl_try_from_robj_for_arrays!(i32);
521impl_try_from_robj_for_arrays!(f64);
522
523// 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)
524// Single-element tuple manually implemented to avoid clippy::needless_question_mark
525
526// We implement the 1-length tuple variant manually
527// so that we avoid clippy needless Ok(?) lint
528impl<T0> TryFrom<&Robj> for (T0,)
529where
530    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
531{
532    type Error = Error;
533
534    fn try_from(robj: &Robj) -> Result<Self> {
535        let list: List = robj.try_into()?;
536        if list.len() != 1 {
537            return Err(Error::ExpectedLength(1));
538        }
539        Ok(((&list.elt(0)?).try_into()?,))
540    }
541}
542
543impl<T0> TryFrom<Robj> for (T0,)
544where
545    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
546{
547    type Error = Error;
548
549    fn try_from(robj: Robj) -> Result<Self> {
550        Self::try_from(&robj)
551    }
552}
553
554impl<T0> TryFrom<&Robj> for Option<(T0,)>
555where
556    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
557{
558    type Error = Error;
559
560    fn try_from(robj: &Robj) -> Result<Self> {
561        if robj.is_null() || robj.is_na() {
562            Ok(None)
563        } else {
564            <(T0,)>::try_from(robj).map(Some)
565        }
566    }
567}
568
569impl<T0> TryFrom<Robj> for Option<(T0,)>
570where
571    T0: for<'a> TryFrom<&'a Robj, Error = Error>,
572{
573    type Error = Error;
574
575    fn try_from(robj: Robj) -> Result<Self> {
576        Self::try_from(&robj)
577    }
578}
579
580impl_try_from_robj_tuples!((2, 12));
581
582// The following is necessary because it is impossible to define `TryFrom<Robj> for &Robj` as
583// it requires returning a reference to a owned (moved) value
584impl TryFrom<&Robj> for HashMap<&str, Robj> {
585    type Error = Error;
586
587    fn try_from(value: &Robj) -> Result<Self> {
588        let value: List = value.try_into()?;
589        Ok(value.into_iter().collect())
590    }
591}
592
593impl TryFrom<&Robj> for HashMap<String, Robj> {
594    type Error = Error;
595    fn try_from(value: &Robj) -> Result<Self> {
596        let value: HashMap<&str, _> = value.try_into()?;
597        Ok(value.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
598    }
599}