Skip to main content

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