Skip to main content

extendr_api/wrapper/
nullable.rs

1use super::*;
2
3/// Wrapper for handling potentially NULL values.
4/// ```
5/// use extendr_api::prelude::*;
6/// test! {
7///     use extendr_api::wrapper::Nullable::*;
8///
9///     // Plain integer.
10///     let s1 = r!(1);
11///     let n1 = <Nullable<i32>>::try_from(&s1)?;
12///     assert_eq!(n1, NotNull(1));
13///
14///     // NA integer - error.
15///     let sna = r!(NA_INTEGER);
16///     assert_eq!(<Nullable<i32>>::try_from(&sna).is_err(), true);
17///
18///     // NA integer - option gives none.
19///     assert_eq!(<Nullable<Option<i32>>>::try_from(&sna)?, NotNull(None));
20///
21///     // NULL object.
22///     let snull = Robj::null();
23///     let nnull = <Nullable<i32>>::try_from(&snull)?;
24///     assert_eq!(nnull, Null);
25///
26///     assert_eq!(r!(Nullable::<i32>::Null), Robj::null());
27///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
28/// }
29/// ```
30#[derive(Debug, PartialEq, Clone)]
31pub enum Nullable<T> {
32    NotNull(T),
33    Null,
34}
35
36impl TryFrom<Robj> for Nullable<()> {
37    type Error = Error;
38
39    fn try_from(value: Robj) -> Result<Self> {
40        Self::try_from(&value)
41    }
42}
43
44impl TryFrom<&Robj> for Nullable<()> {
45    type Error = Error;
46
47    fn try_from(value: &Robj) -> Result<Self> {
48        if value.is_null() {
49            Ok(Self::Null)
50        } else {
51            Err(Error::ExpectedNull(value.clone()))
52        }
53    }
54}
55
56impl<T> TryFrom<Robj> for Nullable<T>
57where
58    T: TryFrom<Robj, Error = Error>,
59{
60    type Error = Error;
61
62    /// Convert an object that may be null to a rust type.
63    /// ```
64    /// use extendr_api::prelude::*;
65    /// test! {
66    ///     let s1 = r!(1);
67    ///     let n1 = <Nullable<i32>>::try_from(s1)?;
68    ///     assert_eq!(n1, Nullable::NotNull(1));
69    ///     let snull = Robj::null();
70    ///     let nnull = <Nullable<i32>>::try_from(snull)?;
71    ///     assert_eq!(nnull, Nullable::Null);
72    /// }
73    /// ```
74    fn try_from(robj: Robj) -> Result<Self> {
75        if robj.is_null() {
76            Ok(Nullable::Null)
77        } else {
78            Ok(Nullable::NotNull(robj.try_into()?))
79        }
80    }
81}
82
83impl<'a, T> TryFrom<&'a Robj> for Nullable<T>
84where
85    T: TryFrom<&'a Robj, Error = Error>,
86{
87    type Error = Error;
88
89    /// Convert an object that may be null to a rust type.
90    /// ```
91    /// use extendr_api::prelude::*;
92    /// test! {
93    ///     let s1 = r!(1);
94    ///     let n1 = <Nullable<i32>>::try_from(&s1)?;
95    ///     assert_eq!(n1, Nullable::NotNull(1));
96    ///     let snull = Robj::null();
97    ///     let nnull = <Nullable<i32>>::try_from(&snull)?;
98    ///     assert_eq!(nnull, Nullable::Null);
99    /// }
100    /// ```
101    fn try_from(robj: &'a Robj) -> Result<Self> {
102        if robj.is_null() {
103            Ok(Nullable::Null)
104        } else {
105            Ok(Nullable::NotNull(robj.try_into()?))
106        }
107    }
108}
109
110impl<T> From<Nullable<T>> for Robj
111where
112    T: Into<Robj>,
113{
114    /// Convert a rust object to NULL or another type.
115    /// ```
116    /// use extendr_api::prelude::*;
117    /// test! {
118    ///     assert_eq!(r!(Nullable::<i32>::Null), Robj::null());
119    ///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
120    /// }
121    /// ```
122    fn from(val: Nullable<T>) -> Self {
123        match val {
124            Nullable::NotNull(t) => t.into(),
125            Nullable::Null => Robj::null(),
126        }
127    }
128}
129
130impl<T> From<Nullable<T>> for Option<T>
131where
132    T: TryFrom<Robj, Error = Error>,
133{
134    /// Convert a Nullable type into Option
135    /// ```
136    /// use extendr_api::prelude::*;
137    /// test! {
138    ///     assert_eq!(<Option<i32>>::from(Nullable::Null), None);
139    ///     assert_eq!(<Option<i32>>::from(Nullable::NotNull(42)), Some(42));
140    /// }
141    /// ```
142    fn from(value: Nullable<T>) -> Self {
143        match value {
144            Nullable::NotNull(value) => Some(value),
145            _ => None,
146        }
147    }
148}
149
150impl<'a, T> From<&'a Nullable<T>> for Option<&'a T>
151where
152    T: TryFrom<Robj, Error = Error>,
153{
154    /// Convert a Nullable reference type into Option containing reference
155    /// ```
156    /// use extendr_api::prelude::*;
157    /// test! {
158    ///     assert_eq!(<Option<&i32>>::from(&Nullable::Null), None);
159    ///     assert_eq!(<Option<&i32>>::from(&Nullable::NotNull(42)), Some(&42));
160    /// }
161    /// ```
162    fn from(value: &'a Nullable<T>) -> Self {
163        match value {
164            Nullable::NotNull(value) => Some(value),
165            _ => None,
166        }
167    }
168}
169
170impl<T> From<Option<T>> for Nullable<T>
171where
172    T: Into<Robj>,
173{
174    /// Convert an Option into Nullable type
175    /// ```
176    /// use extendr_api::prelude::*;
177    /// test! {
178    ///     let x : Nullable<_> = From::<Option<i32>>::from(None);
179    ///     assert_eq!(x, Nullable::<i32>::Null);
180    ///     let x : Nullable<_> = From::<Option<i32>>::from(Some(42));
181    ///     assert_eq!(x, Nullable::<i32>::NotNull(42));
182    /// }
183    /// ```
184    fn from(value: Option<T>) -> Self {
185        match value {
186            Some(value) => Nullable::NotNull(value),
187            _ => Nullable::Null,
188        }
189    }
190}
191
192impl<T> Nullable<T>
193where
194    T: TryFrom<Robj, Error = Error>,
195{
196    /// Convert Nullable R object into `Option`
197    /// ```
198    /// use extendr_api::prelude::*;
199    /// test! {
200    ///     assert_eq!(Nullable::<Rint>::Null.into_option(), None);
201    ///     assert_eq!(Nullable::<Rint>::NotNull(Rint::from(42)).into_option(), Some(Rint::from(42)));
202    /// }
203    /// ```
204    pub fn into_option(self) -> Option<T> {
205        self.into()
206    }
207}
208impl<T> Nullable<T> {
209    /// Map `Nullable<T>` into `Nullable<U>`
210    ///
211    /// ```
212    /// use extendr_api::prelude::*;
213    /// test! {
214    ///     assert_eq!(Nullable::<Rfloat>::Null.map(|x| x.abs()), Nullable::<Rfloat>::Null);
215    ///     assert_eq!(Nullable::<Rfloat>::NotNull(Rfloat::from(42.0)).map(|x| x.abs()), Nullable::<Rfloat>::NotNull(Rfloat::from(42.0)));
216    /// }
217    /// ```
218    pub fn map<F, U>(self, f: F) -> Nullable<U>
219    where
220        F: FnOnce(T) -> U,
221    {
222        match self {
223            Nullable::NotNull(value) => Nullable::NotNull(f(value)),
224            _ => Nullable::Null,
225        }
226    }
227}