miniextendr_api/missing.rs
1//! Support for R's missing arguments.
2//!
3//! When an R function is called without providing a value for a formal argument,
4//! R passes `SEXP::missing_arg()` as a placeholder. This is different from `NULL` -
5//! a missing argument means "not provided", while `NULL` is an explicit value.
6//!
7//! # Example
8//!
9//! ```ignore
10//! use miniextendr_api::{miniextendr, Missing};
11//!
12//! #[miniextendr]
13//! fn greet(name: Missing<String>) -> String {
14//! match name.into_option() {
15//! Some(n) => format!("Hello, {}!", n),
16//! None => "Hello, stranger!".to_string(),
17//! }
18//! }
19//! ```
20//!
21//! In R:
22//! ```r
23//! greet("Alice") # "Hello, Alice!"
24//! greet() # "Hello, stranger!"
25//! ```
26//!
27//! # Difference from `Option<T>`
28//!
29//! - `Option<T>` treats `NULL` as `None` and any other value as `Some(T)`.
30//! - `Missing<T>` treats `SEXP::missing_arg()` as missing and any other value (including `NULL`) as present.
31//!
32//! Use `Missing<Option<T>>` if you need to distinguish between:
33//! - Missing argument (not passed)
34//! - `NULL` (explicitly passed `NULL`)
35//! - A value (explicitly passed a non-NULL value)
36//!
37//! # How it works: the `quote(expr=)` trick
38//!
39//! In regular R functions, you detect missing arguments with `missing()`:
40//!
41//! ```r
42//! my_func <- function(x) {
43//! if (missing(x)) "not provided" else x
44//! }
45//! my_func() # "not provided"
46//! my_func(5) # 5
47//! ```
48//!
49//! However, `.Call()` (the FFI boundary) forces argument evaluation. Without a
50//! default value, R errors before the call even reaches Rust:
51//!
52//! ```r
53//! wrapper <- function(x) .Call(rust_fn, x)
54//! wrapper() # Error: argument "x" is missing, with no default
55//! ```
56//!
57//! The generated R wrappers use `quote(expr=)` as the default for `Missing<T>` parameters:
58//!
59//! ```r
60//! wrapper <- function(x = quote(expr=)) .Call(rust_fn, x)
61//! ```
62//!
63//! `quote(expr=)` is an R idiom that captures the "missing" state - calling `quote()`
64//! without providing its `expr` argument returns `SEXP::missing_arg()`. This allows:
65//!
66//! 1. The call to proceed without error
67//! 2. `SEXP::missing_arg()` to be passed through `.Call()` to Rust
68//! 3. `Missing<T>` to detect it as `Missing::Absent`
69
70use crate::ffi::SEXP;
71use crate::from_r::{SexpError, TryFromSexp};
72
73/// Wrapper type that detects if an R argument was not passed (missing).
74///
75/// This corresponds to R's `missing()` function. When a function parameter
76/// has type `Missing<T>`, it will be `Missing::Absent` if the caller didn't
77/// provide that argument, or `Missing::Present(value)` if they did.
78///
79/// # Example
80///
81/// ```ignore
82/// use miniextendr_api::{miniextendr, Missing};
83///
84/// #[miniextendr]
85/// fn maybe_square(x: Missing<f64>) -> f64 {
86/// match x {
87/// Missing::Present(val) => val * val,
88/// Missing::Absent => 0.0,
89/// }
90/// }
91/// ```
92///
93/// In R:
94/// ```r
95/// maybe_square(5) # 25
96/// maybe_square() # 0
97/// ```
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum Missing<T> {
100 /// The argument was provided.
101 Present(T),
102 /// The argument was not provided (missing in R).
103 Absent,
104}
105
106impl<T> Missing<T> {
107 /// Returns `true` if the argument was provided.
108 #[inline]
109 pub fn is_present(&self) -> bool {
110 matches!(self, Missing::Present(_))
111 }
112
113 /// Returns `true` if the argument was not provided.
114 ///
115 /// Named to match R's `missing()` function.
116 #[inline]
117 pub fn is_missing(&self) -> bool {
118 matches!(self, Missing::Absent)
119 }
120
121 /// Convert to `Option<T>`, returning `None` if missing.
122 #[inline]
123 pub fn into_option(self) -> Option<T> {
124 match self {
125 Missing::Present(v) => Some(v),
126 Missing::Absent => None,
127 }
128 }
129
130 /// Get a reference to the value if present.
131 #[inline]
132 pub fn as_ref(&self) -> Missing<&T> {
133 match self {
134 Missing::Present(v) => Missing::Present(v),
135 Missing::Absent => Missing::Absent,
136 }
137 }
138
139 /// Get a mutable reference to the value if present.
140 #[inline]
141 pub fn as_mut(&mut self) -> Missing<&mut T> {
142 match self {
143 Missing::Present(v) => Missing::Present(v),
144 Missing::Absent => Missing::Absent,
145 }
146 }
147
148 /// Returns the contained value or a default.
149 #[inline]
150 pub fn unwrap_or(self, default: T) -> T {
151 match self {
152 Missing::Present(v) => v,
153 Missing::Absent => default,
154 }
155 }
156
157 /// Returns the contained value or computes it from a closure.
158 #[inline]
159 pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
160 match self {
161 Missing::Present(v) => v,
162 Missing::Absent => f(),
163 }
164 }
165
166 /// Maps `Missing<T>` to `Missing<U>` by applying a function.
167 #[inline]
168 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Missing<U> {
169 match self {
170 Missing::Present(v) => Missing::Present(f(v)),
171 Missing::Absent => Missing::Absent,
172 }
173 }
174
175 /// Returns the contained value, panicking if absent.
176 ///
177 /// # Panics
178 ///
179 /// Panics if the value is `Absent`.
180 #[inline]
181 pub fn unwrap(self) -> T {
182 match self {
183 Missing::Present(v) => v,
184 Missing::Absent => panic!("called `Missing::unwrap()` on an `Absent` value"),
185 }
186 }
187
188 /// Returns the contained value, panicking with a custom message if absent.
189 ///
190 /// # Panics
191 ///
192 /// Panics with the provided message if the value is `Absent`.
193 #[inline]
194 pub fn expect(self, msg: &str) -> T {
195 match self {
196 Missing::Present(v) => v,
197 Missing::Absent => panic!("{}", msg),
198 }
199 }
200}
201
202impl<T: Default> Missing<T> {
203 /// Returns the contained value or the default for that type.
204 #[inline]
205 pub fn unwrap_or_default(self) -> T {
206 match self {
207 Missing::Present(v) => v,
208 Missing::Absent => T::default(),
209 }
210 }
211}
212
213impl<T> Default for Missing<T> {
214 /// The default is `Absent`.
215 #[inline]
216 fn default() -> Self {
217 Missing::Absent
218 }
219}
220
221impl<T> From<T> for Missing<T> {
222 #[inline]
223 fn from(value: T) -> Self {
224 Missing::Present(value)
225 }
226}
227
228impl<T> From<Option<T>> for Missing<T> {
229 #[inline]
230 fn from(opt: Option<T>) -> Self {
231 match opt {
232 Some(v) => Missing::Present(v),
233 None => Missing::Absent,
234 }
235 }
236}
237
238impl<T> From<Missing<T>> for Option<T> {
239 #[inline]
240 fn from(missing: Missing<T>) -> Self {
241 missing.into_option()
242 }
243}
244
245// region: TryFromSexp implementation
246
247/// Check if a SEXP is the missing argument sentinel.
248#[inline]
249pub fn is_missing_arg(sexp: SEXP) -> bool {
250 std::ptr::addr_eq(sexp.0, SEXP::missing_arg().0)
251}
252
253impl<T> TryFromSexp for Missing<T>
254where
255 T: TryFromSexp,
256 <T as TryFromSexp>::Error: Into<SexpError>,
257{
258 type Error = SexpError;
259
260 fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
261 if is_missing_arg(sexp) {
262 Ok(Missing::Absent)
263 } else {
264 T::try_from_sexp(sexp)
265 .map(Missing::Present)
266 .map_err(Into::into)
267 }
268 }
269
270 unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
271 if is_missing_arg(sexp) {
272 Ok(Missing::Absent)
273 } else {
274 unsafe { T::try_from_sexp_unchecked(sexp) }
275 .map(Missing::Present)
276 .map_err(Into::into)
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn missing_methods() {
287 let present: Missing<i32> = Missing::Present(42);
288 let absent: Missing<i32> = Missing::Absent;
289
290 assert!(present.is_present());
291 assert!(!present.is_missing());
292
293 assert!(!absent.is_present());
294 assert!(absent.is_missing());
295
296 assert_eq!(present.into_option(), Some(42));
297 assert_eq!(absent.into_option(), None);
298 }
299
300 #[test]
301 fn missing_unwrap_or() {
302 let present: Missing<i32> = Missing::Present(42);
303 let absent: Missing<i32> = Missing::Absent;
304
305 assert_eq!(present.unwrap_or(0), 42);
306 assert_eq!(absent.unwrap_or(0), 0);
307 }
308
309 #[test]
310 fn missing_map() {
311 let present: Missing<i32> = Missing::Present(21);
312 let absent: Missing<i32> = Missing::Absent;
313
314 assert_eq!(present.map(|x| x * 2), Missing::Present(42));
315 assert_eq!(absent.map(|x| x * 2), Missing::Absent);
316 }
317
318 #[test]
319 fn missing_default() {
320 let m: Missing<i32> = Missing::default();
321 assert!(m.is_missing());
322 }
323
324 #[test]
325 fn missing_from_option() {
326 let some: Missing<i32> = Some(42).into();
327 let none: Missing<i32> = None.into();
328
329 assert_eq!(some, Missing::Present(42));
330 assert_eq!(none, Missing::Absent);
331 }
332}
333// endregion