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