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}